diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6221038e1ec..d41be22a051 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -16,6 +16,7 @@ /feature/bgp/ @openconfig/featureprofiles-owner-bgp /feature/dhcp/ @alokmtri-g /feature/ethernet/ @ram-mac +/feature/gribi/mpls_in_udp/ @openconfig/featureprofiles-owner-gribi-mpls-in-udp /feature/gribi/ @openconfig/featureprofiles-owner-gribi /feature/interface/ @openconfig/featureprofiles-owner-interface /feature/isis/ @openconfig/featureprofiles-owner-isis @@ -23,15 +24,16 @@ /feature/mpls/ @openconfig/featureprofiles-owner-mpls /feature/mtu/ @openconfig/featureprofiles-owner-mtu /feature/networkinstance/ @openconfig/featureprofiles-owner-networkinstance -/feature/platform/ @openconfig/featureprofiles-owner-platform /feature/platform/transceiver @openconfig/featureprofiles-owner-platform-transceiver -/feature/qos @openconfig/featureprofiles-owner-qos +/feature/platform/ @openconfig/featureprofiles-owner-platform +/feature/qos/ @openconfig/featureprofiles-owner-qos /feature/routing_policy/ @swetha-haridasula /feature/sampling/ @masood-shah /feature/security @openconfig/featureprofiles-owner-security +/feature/gnsi/ @openconfig/featureprofiles-owner-security /feature/staticroute/ @openconfig/featureprofiles-owner-staticroute /feature/stp/ @alokmtri-g -/feature/system @openconfig/featureprofiles-owner-system +/feature/system/ @openconfig/featureprofiles-owner-system /feature/vrrp @amrindrr # Common OTG utilities diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 94fa49436a2..8af51848060 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -15,7 +15,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version-file: 'go.mod' - name: Cache uses: actions/cache@v4 with: @@ -23,8 +23,12 @@ jobs: ~/go/pkg/mod ~/.cache/go-build key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} + # Dependency for Go module github.com/google/gopacket + - name: Install libpcap-dev + run: sudo apt-get -y install libpcap-dev - name: Build run: go build -v ./... + test: runs-on: ubuntu-latest steps: @@ -32,7 +36,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v5 with: - go-version: 1.23.x + go-version-file: 'go.mod' - name: Cache uses: actions/cache@v4 with: @@ -45,17 +49,20 @@ jobs: run: sudo apt-get -y install libpcap-dev - run: go test -v -coverprofile=profile.cov $(go list ./... | grep -v /.*test.*) - name: Send coverage + continue-on-error: true uses: shogo82148/actions-goveralls@7b1bd2871942af030d707d6574e5f684f9891fb2 with: path-to-profile: profile.cov + static_analysis: name: Static Analysis runs-on: ubuntu-latest steps: - - name: Install go + - uses: actions/checkout@v4 + - name: Set up Go uses: actions/setup-go@v5 with: - go-version: '1.23' + go-version-file: 'go.mod' # Go & staticcheck build cache require a lot of disk space. Reclaim extra # space for the container by removing unnecessary tooling. - name: Free additional disk space @@ -65,8 +72,6 @@ jobs: sudo rm -rf /opt/hostedtoolcache/CodeQL sudo mv "${HOME}/.cache" /mnt/cache ln -s /mnt/cache "${HOME}/.cache" - - name: Checkout code - uses: actions/checkout@v4 - name: Cache uses: actions/cache@v4 with: @@ -78,6 +83,12 @@ jobs: # Dependency for Go module github.com/google/gopacket - name: Install libpcap-dev run: sudo apt-get -y install libpcap-dev + + # === THIS IS THE ONLY CHANGE === + - name: Clean Go build cache before analysis + run: go clean -cache + # =============================== + - name: Go vet run: GOGC=30 go vet ./... - name: Gofmt diff --git a/.github/workflows/protobufs.yml b/.github/workflows/protobufs.yml index e37f7ed27d0..1066677c1a7 100644 --- a/.github/workflows/protobufs.yml +++ b/.github/workflows/protobufs.yml @@ -43,42 +43,3 @@ jobs: for i in `find . -name \*.textproto`; do textproto-validator -I ./protobuf-import $i done - - validate_oc_paths: - name: Validate OpenConfig Paths - runs-on: ubuntu-latest - steps: - - name: Install go - uses: actions/setup-go@v5 - with: - go-version: '1.23' - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - name: Cache - uses: actions/cache@v4 - with: - path: | - ~/go/pkg/mod - ~/.cache/go-build - key: ${{ github.job }}-${{ runner.os }}-go-build-${{ hashFiles('**/go.sum') }} - - name: Fetch OpenConfig Models - run: make openconfig_public - - name: Validate Paths - run: | - # https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables - if [ ! -z "${GITHUB_BASE_REF}" ]; then - readonly HEAD=${{ github.event.pull_request.head.sha }} - readonly BASE="$(git merge-base origin/main "${HEAD}")" - if ! git diff --diff-filter=D --name-only "${BASE}" | grep -E 'feature.textproto$'; then - # If it is a pull request AND if no feature.textproto files were - # deleted, then we can skip checking all but the added/modified - # feature.textproto files. - export FEATURE_FILES=changed-feature-textprotos.githubactions.txt - # grep: don't error out on no match. - git diff --diff-filter=d --name-only "${BASE}" | { grep -E 'feature.textproto$' || true; } > "${FEATURE_FILES}" - fi - fi - make validate_paths - diff --git a/.github/workflows/readme_canonicaloc.yml b/.github/workflows/readme_canonicaloc.yml new file mode 100644 index 00000000000..fb8a854ef09 --- /dev/null +++ b/.github/workflows/readme_canonicaloc.yml @@ -0,0 +1,91 @@ +name: README Canonical OC + +on: + push: + branches: [ main ] + pull_request: + schedule: + - cron: "49 0 * * *" + +jobs: + integration-test: + runs-on: ubuntu-latest + steps: + - name: Check out code + uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 + with: + fetch-depth: 0 + - name: Set up Go + uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe + with: + go-version-file: 'go.mod' + cache: false + + - name: Validate Canonical OC Validation Script + run: | + cd tools/validate_readme_canonicalocspec + ./validate_readme_canonicalocspec_test.sh + + - name: Validate Template README + run: | + go install ./tools/validate_readme_canonicalocspec + validate_readme_canonicalocspec --alsologtostderr doc/test-requirements-template.md + + - name: Validate Test READMEs + run: | + go install ./tools/validate_readme_canonicalocspec + + # TODO: Just use this one line after all READMEs have converted to the new format. + # validate_readme_canonicalocspec --alsologtostderr "${exemption_flags[@]}" + + function validate() { + local exemption_flags=() + while IFS= read -r path; do + exemption_flags+="--non-test-readme $path " + done < ./tools/non_test_readmes.txt + validate_readme_canonicalocspec --feature-dir "$1" --alsologtostderr ${exemption_flags} + } + + ##### BEGIN: Validate Changed Test READMEs # TODO: Remove this section after all are converted. + + # Adapted from rebase_check.yml + # Notes: + # * Do not use ${GITHUB_REF}, github.sha, or HEAD because they are + # the merged commit of the pull request and main. There are no + # outdated files in the merged commit. + # * refs/pull/${pr_number}/head is not available, so use + # github.event.pull_request.head.sha which is the "head sha" of + # the event that triggered the pull request. + # * Do not use github.event.pull_request.base.sha because it is + # the base when the pull request was created, not after a rebase. + # Ask git merge-base to tell us a suitable base. + readonly HEAD="${{ github.event.pull_request.head.sha }}" + if [ ! -z "${HEAD}" ]; then + readonly BASE="$(git merge-base origin/main "${HEAD}")" + + affected_readmes=() + for path in $(git diff --name-only --diff-filter=ACMRT "${BASE}" "${HEAD}" | grep -E '^\W*feature' | sort -u); do + if [[ "$path" == *README.md ]]; then + affected_readmes+=("$path") + fi + done + + echo "########## READMEs in changed directories to be validated (including ones to be exempted):" + printf '%s\n' "${affected_readmes[@]}" + + echo "########## Validating READMEs in changed directories:" + exemption_flags=() + while IFS= read -r path; do + exemption_flags+="--non-test-readme $path " + done < ./tools/non_test_readmes.txt + for f in "${affected_readmes[@]}"; do + validate_readme_canonicalocspec --alsologtostderr ${exemption_flags} "${f}" + done + fi + + ##### END: Validate Changed Test READMEs ##### + + # TODO: Validate READMEs that have been converted to the new format. + # echo "########## Validating already-converted READMEs:" + + diff --git a/.github/workflows/readme_oc_path_and_rpc.yml b/.github/workflows/readme_oc_path_and_rpc.yml index 23215b74d7a..95fc357a621 100644 --- a/.github/workflows/readme_oc_path_and_rpc.yml +++ b/.github/workflows/readme_oc_path_and_rpc.yml @@ -35,17 +35,15 @@ jobs: run: | go install ./tools/validate_readme_spec - exemption_flags=( - --non-test-readme feature/security/gnsi/certz/test_data/README.md - --non-test-readme feature/p4rt/README.md - --non-test-readme feature/security/gnsi/acctz/README.md - ) - # TODO: Just use this one line after all READMEs have converted to the new format. # validate_readme_spec --alsologtostderr "${exemption_flags[@]}" function validate() { - validate_readme_spec --feature-dir "$1" --alsologtostderr "${exemption_flags[@]}" + local exemption_flags=() + while IFS= read -r path; do + exemption_flags+="--non-test-readme $path " + done < ./tools/non_test_readmes.txt + validate_readme_spec --feature-dir "$1" --alsologtostderr ${exemption_flags} } ##### BEGIN: Validate Changed Test READMEs # TODO: Remove this section after all are converted. @@ -76,15 +74,19 @@ jobs: printf '%s\n' "${affected_readmes[@]}" echo "########## Validating READMEs in changed directories:" + exemption_flags=() + while IFS= read -r path; do + exemption_flags+="--non-test-readme $path " + done < ./tools/non_test_readmes.txt for f in "${affected_readmes[@]}"; do - validate_readme_spec --alsologtostderr "${exemption_flags[@]}" "${f}" + validate_readme_spec --alsologtostderr ${exemption_flags} "${f}" done fi ##### END: Validate Changed Test READMEs ##### echo "########## Validating already-converted READMEs:" - validate feature/aft + validate feature/afts validate feature/bgp/policybase/otg_tests/import_export_multi_test validate feature/gnmi validate feature/gnoi @@ -94,6 +96,7 @@ jobs: validate feature/security validate feature/staticroute validate feature/system/management - validate feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test + validate feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test validate feature/system/ntp/tests/system_ntp_test validate feature/qos/otg_tests/bursty_traffic_test + diff --git a/.github/workflows/required_approvals.yml b/.github/workflows/required_approvals.yml index 4a2d7789d59..faa16556f19 100644 --- a/.github/workflows/required_approvals.yml +++ b/.github/workflows/required_approvals.yml @@ -1,4 +1,3 @@ -# This workflow is to make sure we have 1 LGTM for business logic and 1 LGTM for code style name: PR Approval Workflow on: pull_request_target: @@ -21,5 +20,5 @@ jobs: org_name: openconfig min_approvals: 1 approval_mode: ALL - pr_number: ${{ github.event.number }} + pr_number: ${{ github.event.pull_request.number }} require_all_approvals_latest_commit: false diff --git a/Makefile b/Makefile index 79898629989..f4271715302 100644 --- a/Makefile +++ b/Makefile @@ -39,10 +39,12 @@ protoimports: find protobuf-import -type d -empty -delete # Download the required dependencies go mod download - # Get ondatra & kne modules we use and create required directory structure + # Get gnmi, ondatra & kne modules we use and create required directory structure + go list -f 'protobuf-import/{{ .Path }}' -m github.com/openconfig/gnmi | xargs -L1 dirname | sort | uniq | xargs mkdir -p go list -f 'protobuf-import/{{ .Path }}' -m github.com/openconfig/ondatra | xargs -L1 dirname | sort | uniq | xargs mkdir -p go list -f 'protobuf-import/{{ .Path }}' -m github.com/openconfig/kne | xargs -L1 dirname | sort | uniq | xargs mkdir -p # Create symlinks + go list -f '{{ .Dir }} protobuf-import/{{ .Path }}' -m github.com/openconfig/gnmi | xargs -L1 -- ln -s go list -f '{{ .Dir }} protobuf-import/{{ .Path }}' -m github.com/openconfig/ondatra | xargs -L1 -- ln -s go list -f '{{ .Dir }} protobuf-import/{{ .Path }}' -m github.com/openconfig/kne | xargs -L1 -- ln -s ln -s $(ROOT_DIR) protobuf-import/github.com/openconfig/featureprofiles @@ -85,3 +87,4 @@ topologies/proto/binding/binding.pb.go: topologies/proto/binding.proto protoimpo clean: rm -f $(GO_PROTOS) rm -rf protobuf-import openconfig_public + diff --git a/doc/canonical_oc_generation_example/main.go b/doc/canonical_oc_generation_example/main.go new file mode 100644 index 00000000000..a57862b8a57 --- /dev/null +++ b/doc/canonical_oc_generation_example/main.go @@ -0,0 +1,71 @@ +// Package main provides an example program that outputs specific JSON for an OpenConfig schema. +// It demonstrates how to use the ygot library to build and marshal OpenConfig data structures. +// The program requires the YANG for a particular field to be defined. +package main + +import ( + "fmt" + + "github.com/golang/glog" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +// This is an example program that outputs specific JSON for an OpenConfig +// schema. It requires the YANG for a particular field to be defined such +// that it can be used. +// +// You can find documentation of the schema on openconfig.net. +// Particularly: +// +// - https://openconfig.net/projects/models/paths/ - searchable list of paths. +// - https://openconfig.net/projects/models/schemadocs/ - documentation for each path. +// +// Expected output for the program run: +// +// { +// "interfaces": { +// "interface": [ +// { +// "config": { +// "description": "a description", +// "mtu": 1500, +// "name": "eth0", +// "type": "ethernetCsmacd" +// }, +// "hold-time": { +// "config": { +// "up": 42 +// } +// }, +// "name": "eth0" +// } +// ] +// }, +// "system": { +// "config": { +// "hostname": "a hostname" +// } +// } +// } +func main() { + d := &oc.Root{} + + d.GetOrCreateInterface("eth0").GetOrCreateHoldTime().Up = ygot.Uint32(42) + d.GetOrCreateInterface("eth0").Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + d.GetOrCreateInterface("eth0").Description = ygot.String("a description") + d.GetOrCreateInterface("eth0").Mtu = ygot.Uint16(1500) + + d.GetOrCreateSystem().Hostname = ygot.String("a hostname") + + fmt.Printf("%v\n", renderJSON(d)) +} + +func renderJSON(s ygot.GoStruct) string { + bs, err := ygot.Marshal7951(s, ygot.JSONIndent(" "), &ygot.RFC7951JSONConfig{PreferShadowPath: true}) + if err != nil { + glog.Exitf("cannot marshal JSON, %v", err) + return "" + } + return string(bs) +} diff --git a/doc/test-requirements-template.md b/doc/test-requirements-template.md index 5d18b096163..50e3143343e 100644 --- a/doc/test-requirements-template.md +++ b/doc/test-requirements-template.md @@ -50,9 +50,17 @@ be generated by the subtest. This configuration should be in JSON format. NOTE: If the test requires configuration or state which are not defined in the [public OC data models]([url](https://github.com/openconfig/public)), then new OC -paths must be proposed in this README. A "TODO" comment must be provided linking -to a pull request to add these paths to the public OC models repository. The -README may be approved and merged before the TODO item(s) are resolved. +paths must be proposed in the "OpenConfig Path and RPC Coverage" heading for this +README. A "TODO" comment must be provided linking to a pull request to add these +"proposed paths" to the public OC models repository should be provided. The README +may be approved and merged before the TODO item(s) are resolved. + +At least one "Canonical OC" heading must be present with JSON content in the README. +This JSON may only include OC paths defined in the master branch of the +[openconfig/public](https://github.com/openconfig/public/tree/master) data models. Any +"proposed paths" must be left out of the "Canonical OC" JSON. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy ```json { @@ -75,7 +83,7 @@ README may be approved and merged before the TODO item(s) are resolved. "scheduler-policy": { "config": { "name": "limit_group_A_1Gb", - "new-leaf": "my_new_value" # TODO https://github.com/openconfig/public/pull/1234 + "new-leaf": "my_new_value" } } }, @@ -94,6 +102,68 @@ README may be approved and merged before the TODO item(s) are resolved. Repeat the format of the first subtest for each additional subtest defined. +#### Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "config": { + "neighbor-address": "192.0.2.6" + }, + "neighbor-address": "192.0.2.6", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "config": { + "peer-group-name": "peer_group" + }, + "peer-group-name": "peer_group", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + } +} +``` + ## OpenConfig Path and RPC Coverage This yaml stanza defines the OC paths intended to be covered by this test. OC diff --git a/feature/acl/feature.textproto b/feature/acl/feature.textproto deleted file mode 100644 index a85969cdf78..00000000000 --- a/feature/acl/feature.textproto +++ /dev/null @@ -1,125 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "acl" - version: 1 -} - -# ACL-SETS -config_path { - path: "/acl/acl-sets/acl-set/config/name" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/state/name" -} -config_path { - path: "/acl/acl-sets/acl-set/config/type" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/state/type" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/config/description" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/state/description" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/config/sequence-id" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/state/sequence-id" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/source-address" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/state/source-address" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/actions/config/forwarding-action" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/actions/state/forwarding-action" -} - -# ACL defined-sets -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/destination-address-prefix-set" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/config/source-address-prefix-set" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/state/destination-address-prefix-set" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv4/state/source-address-prefix-set" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/destination-address-prefix-set" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/config/source-address-prefix-set" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/state/destination-address-prefix-set" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/ipv6/state/source-address-prefix-set" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/destination-port-set" -} -config_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/transport/config/source-port-set" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/transport/state/destination-port-set" -} -telemetry_path { - path: "/acl/acl-sets/acl-set/acl-entries/acl-entry/transport/state/source-port-set" -} - - - -# INTERFACES - -config_path { - path: "/acl/interfaces/interface/config/id" -} -telemetry_path { - path: "/acl/interfaces/interface/state/id" -} -config_path { - path: "/acl/interfaces/interface/interface-ref/config/interface" -} -telemetry_path { - path: "/acl/interfaces/interface/interface-ref/state/interface" -} -config_path { - path: "/acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/config/type" -} -telemetry_path { - path: "/acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/state/type" -} -config_path { - path: "/acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/config/set-name" -} -telemetry_path { - path: "/acl/interfaces/interface/ingress-acl-sets/ingress-acl-set/state/set-name" -} diff --git a/feature/acl/otg_tests/acl_dscp_match/acl_dscp_match_test.go b/feature/acl/otg_tests/acl_dscp_match/acl_dscp_match_test.go new file mode 100644 index 00000000000..af4efd8f3a0 --- /dev/null +++ b/feature/acl/otg_tests/acl_dscp_match/acl_dscp_match_test.go @@ -0,0 +1,355 @@ +package acl_dscp_match_test + +import ( + "fmt" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + aclName = "ip_dscp_match" + trafficTimeout = 10 * time.Second + IPv4 = "IPv4" + IPv6 = "IPv6" + trafficFrameSize = 512 + trafficRatePps = 1000 + noOfPackets = 5000 + + ipProtoTCP = 6 + AF11 uint32 = 10 + AF21 uint32 = 18 + AF31 uint32 = 26 + AF41 uint32 = 34 + ipProtoICMPv6 = 58 + ipv4PrefixLen = 32 + ipv6PrefixLen = 128 +) + +var ( + // DUT ports + dutPort1 = attrs.Attributes{ + Name: "port1", + Desc: "Dut port 1", + IPv4: "192.168.1.1", + IPv4Len: 30, + IPv6: "2001:DB8::1", + IPv6Len: 126, + } + + dutPort2 = attrs.Attributes{ + Name: "port2", + Desc: "Dut port 2", + IPv4: "192.168.1.5", + IPv4Len: 30, + IPv6: "2001:DB8::5", + IPv6Len: 126, + } + + // ATE ports + otgPort1 = attrs.Attributes{ + Desc: "Otg port 1", + Name: "port1", + MAC: "00:01:12:00:00:01", + IPv4: "192.168.1.2", + IPv4Len: 30, + IPv6: "2001:DB8::2", + IPv6Len: 126, + } + + otgPort2 = attrs.Attributes{ + Desc: "Otg port 2", + Name: "port2", + MAC: "00:01:12:00:00:02", + IPv4: "192.168.1.6", + IPv4Len: 30, + IPv6: "2001:DB8::6", + IPv6Len: 126, + } + + atePortPair = []attrs.Attributes{otgPort1, otgPort2} +) + +type testCase struct { + name string + ipType string + srcDstPortPair []uint32 + dscpTestValues map[uint32]bool + flowName string + aclDscpValue uint32 +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestAclDscpMatch(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + + configureDUT(t, dut) + + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + + otgPort1.AddToOTG(top, ap1, &dutPort1) + otgPort2.AddToOTG(top, ap2, &dutPort2) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + testCases := []testCase{ + { + name: "ACL-1.1.1 - IPv4 Address and DSCP", + ipType: IPv4, + flowName: "ACL-1.1.1", + dscpTestValues: map[uint32]bool{AF11: true, AF21: false}, + aclDscpValue: AF11, + }, + { + name: "ACL-1.1.2 - IPv4 Address, TCP src/dst ports and DSCP", + ipType: IPv4, + srcDstPortPair: []uint32{49256, 49512}, + dscpTestValues: map[uint32]bool{AF21: true, AF31: false}, + flowName: "ACL-1.1.2", + aclDscpValue: AF21, + }, + { + name: "ACL-1.1.3 - IPv6 Address and DSCP", + ipType: IPv6, + flowName: "ACL-1.1.3", + dscpTestValues: map[uint32]bool{AF31: true, AF21: false}, + aclDscpValue: AF31, + }, + { + name: "ACL-1.1.4 - IPv6 Address, TCP src/dst ports and DSCP", + ipType: IPv6, + srcDstPortPair: []uint32{49256, 49512}, + dscpTestValues: map[uint32]bool{AF41: true, AF11: false}, + flowName: "ACL-1.1.4", + aclDscpValue: AF41, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runTest(t, tc, dut, ate, &top) + }) + } +} + +func runTest(t *testing.T, tc testCase, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, config *gosnappi.Config) { + otg := ate.OTG() + t.Logf("Configuring ACLs for testcase %s for DSCP %d", tc.name, tc.aclDscpValue) + configureAcl(t, dut, tc) + + for dscp, expectPass := range tc.dscpTestValues { + if expectPass { + t.Logf("Expecting traffic to pass for DSCP %d", dscp) + } else { + t.Logf("Expecting traffic to be dropped for DSCP %d", dscp) + } + configureFlows(t, config, tc, dscp) + otg.PushConfig(t, *config) + otg.StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), *config, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), *config, "IPv6") + otg.StartTraffic(t) + waitForTraffic(t, otg, tc.flowName, trafficTimeout) + + otgutils.LogFlowMetrics(t, otg, *config) + otgutils.LogPortMetrics(t, otg, *config) + + flowMetrics := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(tc.flowName).State()) + if *flowMetrics.Counters.OutPkts == 0 { + t.Errorf("No packets transmitted") + } + if expectPass { + message := fmt.Sprintf("Expected %d packets, got %d", noOfPackets, *flowMetrics.Counters.InPkts) + if *flowMetrics.Counters.InPkts != noOfPackets { + t.Error(message) + } else { + t.Log(message) + } + } else { + message := fmt.Sprintf("Expected 0 packets, got %d", *flowMetrics.Counters.InPkts) + if *flowMetrics.Counters.InPkts != 0 { + t.Error(message) + } else { + t.Log(message) + } + } + } +} + +func waitForTraffic(t *testing.T, otg *otg.OTG, flowName string, timeout time.Duration) { + transmitPath := gnmi.OTG().Flow(flowName).Transmit().State() + _, ok := gnmi.Watch(t, otg, transmitPath, timeout, func(val *ygnmi.Value[bool]) bool { + transmitState, present := val.Val() + return present && !transmitState + }).Await(t) + + if !ok { + t.Errorf("Traffic for flow %s did not stop within the timeout of %d", flowName, timeout) + } else { + t.Logf("Traffic for flow %s has stopped", flowName) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + + t.Logf("Configuring Interfaces") + configureDUTPort(t, dut, &dutPort1, dp1) + configureDUTPort(t, dut, &dutPort2, dp2) +} + +func configureDUTPort(t *testing.T, dut *ondatra.DUTDevice, attrs *attrs.Attributes, p *ondatra.Port) { + t.Helper() + d := gnmi.OC() + i := attrs.NewOCInterface(p.Name(), dut) + i.Description = ygot.String(attrs.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + i.GetOrCreateEthernet() + i4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + i4.Enabled = ygot.Bool(true) + a := i4.GetOrCreateAddress(attrs.IPv4) + a.PrefixLength = ygot.Uint8(attrs.IPv4Len) + + i6 := i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + i6.Enabled = ygot.Bool(true) + a6 := i6.GetOrCreateAddress(attrs.IPv6) + a6.PrefixLength = ygot.Uint8(attrs.IPv6Len) + + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), i) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p.Name(), deviations.DefaultNetworkInstance(dut), 0) + t.Logf("DUT %s %s %s requires explicit interface in default VRF deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p) + } +} + +func configureFlows(t *testing.T, config *gosnappi.Config, tc testCase, dscp uint32) { + (*config).Flows().Clear() + flow := (*config).Flows().Add().SetName(tc.flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{fmt.Sprintf("%s.%s", atePortPair[0].Name, tc.ipType)}).SetRxNames([]string{fmt.Sprintf("%s.%s", atePortPair[1].Name, tc.ipType)}) + flow.Size().SetFixed(trafficFrameSize) + flow.Rate().SetPps(trafficRatePps) + flow.Duration().SetFixedPackets(gosnappi.NewFlowFixedPackets().SetPackets(noOfPackets)) + + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(atePortPair[0].MAC) + + switch tc.ipType { + case IPv4: + ipv4 := flow.Packet().Add().Ipv4() + ipv4.Src().SetValue(atePortPair[0].IPv4) + ipv4.Dst().SetValue(atePortPair[1].IPv4) + ipv4.Priority().Dscp().Phb().SetValue(dscp) + case IPv6: + ipv6 := flow.Packet().Add().Ipv6() + ipv6.Src().SetValue(atePortPair[0].IPv6) + ipv6.Dst().SetValue(atePortPair[1].IPv6) + ipv6.TrafficClass().SetValue(dscp << 2) + default: + t.Errorf("Invalid traffic type %s", tc.ipType) + } + if len(tc.srcDstPortPair) == 2 { + tcp := flow.Packet().Add().Tcp() + tcp.SrcPort().SetValue(tc.srcDstPortPair[0]) + tcp.DstPort().SetValue(tc.srcDstPortPair[1]) + } +} + +func configureAclInterface(t *testing.T, dut *ondatra.DUTDevice, acl *oc.Acl, tc testCase) { + ifName := dut.Port(t, "port1").Name() + + existingIface := gnmi.Get(t, dut, gnmi.OC().Interface(ifName).State()) + + iFace := acl.GetOrCreateInterface(ifName) + if tc.ipType == IPv4 { + iFace.GetOrCreateIngressAclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4) + } else { + iFace.GetOrCreateIngressAclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV6) + } + + iFace.GetOrCreateInterfaceRef().Interface = existingIface.Name + iFace.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) +} + +func configureAcl(t *testing.T, dut *ondatra.DUTDevice, tc testCase) { + t.Helper() + d := &oc.Root{} + acl := d.GetOrCreateAcl() + var aclSet *oc.Acl_AclSet + if tc.ipType == IPv4 { + aclSet = acl.GetOrCreateAclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4) + } else { + aclSet = acl.GetOrCreateAclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV6) + } + + aclAcceptEntry := aclSet.GetOrCreateAclEntry(10) + if tc.ipType == IPv4 { + ipv4Acl := aclAcceptEntry.GetOrCreateIpv4() + ipv4Acl.SourceAddress = ygot.String(fmt.Sprintf("%s/%d", otgPort1.IPv4, ipv4PrefixLen)) + ipv4Acl.DestinationAddress = ygot.String(fmt.Sprintf("%s/%d", otgPort2.IPv4, ipv4PrefixLen)) + ipv4Acl.SetDscp(uint8(tc.aclDscpValue)) + } else { + ipv6Acl := aclAcceptEntry.GetOrCreateIpv6() + ipv6Acl.SourceAddress = ygot.String(fmt.Sprintf("%s/%d", otgPort1.IPv6, ipv6PrefixLen)) + ipv6Acl.DestinationAddress = ygot.String(fmt.Sprintf("%s/%d", otgPort2.IPv6, ipv6PrefixLen)) + ipv6Acl.SetDscp(uint8(tc.aclDscpValue)) + } + if len(tc.srcDstPortPair) == 2 { + if tc.ipType == IPv4 { + aclAcceptEntry.GetOrCreateIpv4().SetProtocol(oc.UnionUint8(ipProtoTCP)) + } else { + aclAcceptEntry.GetOrCreateIpv6().SetProtocol(oc.UnionUint8(ipProtoTCP)) + } + aclTransport := aclAcceptEntry.GetOrCreateTransport() + aclTransport.SetSourcePort(oc.UnionUint16(tc.srcDstPortPair[0])) + aclTransport.SetDestinationPort(oc.UnionUint16(tc.srcDstPortPair[1])) + } + aclAcceptEntry.GetOrCreateActions().SetForwardingAction(oc.Acl_FORWARDING_ACTION_ACCEPT) + + if tc.ipType == IPv6 { + // Adding allow rule for IPV6 ND packets + aclARPEntry := aclSet.GetOrCreateAclEntry(15) + aclARPEntry.Description = ygot.String("allow ND packets") + aclMatchipv6 := aclARPEntry.GetOrCreateIpv6() + aclMatchipv6.Protocol = oc.UnionUint8(ipProtoICMPv6) + aclARPEntry.GetOrCreateActions().ForwardingAction = oc.Acl_FORWARDING_ACTION_ACCEPT + } + + // Add a deny all rule at the end + aclDropEntry := aclSet.GetOrCreateAclEntry(20) + aclDropEntry.Description = ygot.String("dscp mismatch drop") + aclDropEntry.GetOrCreateActions().SetForwardingAction(oc.Acl_FORWARDING_ACTION_DROP) + + configureAclInterface(t, dut, acl, tc) + gnmi.Replace(t, dut, gnmi.OC().Acl().Config(), acl) +} diff --git a/feature/isis/otg_tests/graceful_restart_helper/metadata.textproto b/feature/acl/otg_tests/acl_dscp_match/metadata.textproto similarity index 52% rename from feature/isis/otg_tests/graceful_restart_helper/metadata.textproto rename to feature/acl/otg_tests/acl_dscp_match/metadata.textproto index 0c495e60d7a..3a5bff382c1 100644 --- a/feature/isis/otg_tests/graceful_restart_helper/metadata.textproto +++ b/feature/acl/otg_tests/acl_dscp_match/metadata.textproto @@ -1,9 +1,9 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata -uuid: "4ebdc9b9-8f58-4e33-875b-b5b2a3e0de33" -plan_id: "RT-2.16" -description: "IS-IS Graceful Restart Helper" +uuid: "e31cac3b-c49c-4530-8e2e-8357b25f13fe" +plan_id: "ACL-1.1" +description: "ACL match based on L3/L4 fields and DSCP value" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { @@ -12,9 +12,6 @@ platform_exceptions: { deviations: { interface_enabled: true default_network_instance: "default" - omit_l2_mtu: true - isis_interface_afi_unsupported: true - isis_instance_enabled_required: true - missing_value_for_defaults: true } -} \ No newline at end of file +} +tags: TAGS_AGGREGATION diff --git a/feature/acl/otg_tests/acl_large_scale/metadata.textproto b/feature/acl/otg_tests/acl_large_scale/metadata.textproto new file mode 100644 index 00000000000..a50c991a039 --- /dev/null +++ b/feature/acl/otg_tests/acl_large_scale/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5172ac27-b07b-42dd-93e6-f751b2a671bf" +plan_id: "ACL-1.3" +description: "Large Scale ACL with TCAM profile" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/acl/otg_tests/acl_update_test/metadata.textproto b/feature/acl/otg_tests/acl_update_test/metadata.textproto new file mode 100644 index 00000000000..62103f11c84 --- /dev/null +++ b/feature/acl/otg_tests/acl_update_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "733c243c-96d1-4ca1-8f7e-199f9634f22e" +plan_id: "ACL-1.2" +description: "ACL Update (Make-before-break)" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/aft/aft_base/otg_tests/afts_base/README.md b/feature/aft/aft_base/otg_tests/afts_base/README.md deleted file mode 100644 index b2e0d5a2792..00000000000 --- a/feature/aft/aft_base/otg_tests/afts_base/README.md +++ /dev/null @@ -1,177 +0,0 @@ -# AFT-1.1: AFTs Base - -## Summary - -IPv4/IPv6 unicast routes next hop group and next hop. - -## Testbed - -* atedut_4.testbed - -## Test Setup - -### Generate DUT and ATE Configuration - -Configure DUT:port1,port2,port3 for IS-IS session with ATE:port1,port2,port3 -* IS-IS must be level 2 only with wide metric. -* IS-IS must be point to point. -* Send 1000 ipv4 and 1000 ipv6 IS-IS prefixes from ATE:port3 to DUT:port3. - - -Establish eBGP sessions between ATE:port1,port2 and DUT:port1,port2 and another between ATE:port3 and DUT:port3. -* Configure eBGP over the interface ip. -* eBGP must be multipath. -* Advertise 1000 ipv4,ipv6 prefixes from ATE port1,port2 observe received prefixes at DUT. -* Validate total number of entries of AFT for IPv4 and IPv6. -* Each prefix must have 2 next hops pointing to ATE port1,port2. -* Advertise 100 ipv4,ipv6 from ATE port3 observe received prefixes at DUT. - -Establish RSVP Sessions between ATE:port3 and SUT:port3. -* Configure mpls and rsvp sessions. -* Configure 2 ingress TE tunnels from DUT:port3 to ATE:port3. -* Tunnel destination is interface ip of ATE:port3. -* Configure explicit null and ipv6 tunneling. -* BGP advertised routes from ATE:port3 must be pointing to the 2 tunnels in the DUT. - -### Procedure - -* Use gNMI.Set with REPLACE option to push the Test Setup configuration to the DUT. -* ATE configuration must be pushed. - -### Verifications - -* BGP route advertised from ATE:port1,port2 must have 2 nexthops. -* IS-IS route advertised from ATE:port3 must have one next hop. -* BGP route advertised from ATE:port3 must have 2 next hops pointing to tunnels. -* Use gnmi Subscribe with ON_CHANGE option to /network-instances/network-instance/afts. -* For verifying prefix, nexthop groups, next hop use the leaves mentioed in the path section. -* Verify afts prefix advertised by BGP,ISIS. -* Verify its next hop group, number of next hop and its interfaces. -* Verify the number of next hop is same as expected. -* Verify all other leaves mentioned in the path section. - - -## AFT-1.1.1: AFT Base Link Down scenario 1 - -### Procedure - -Bring down the link between ATE:port2 and DUT:port2 using OTG api. - -### Verifications - -* BGP routes advertised from ATE:port1,port2 must have 1 nexthop. -* IS-IS routes advertised from ATE:port3 must have one next hop. -* BGP routes advertised from ATE:port3 must have 2 next hops pointing to tunnels. -* For verifying prefix, nexthop groups, next hop use the leaves mentioed in the path section. -* Verify afts prefix advertised by BGP,ISIS. -* Verify its next hop group, number of next hop and its interfaces. -* Verify the number of next hop is same as expected. - -## AFT-1.1.2: AFT Base Link Down scenario 2 - -### Procedure - -Bring down both links between ATE:port1,port2 and DUT:port1,port2 using OTG api. - -### Verifications - -* BGP routes advertised from ATE:port1,port2 must be removed from RIB,FIB of the DUT, query results nil. -* IS-IS routes advertised from ATE:port3 must have one next hop. -* BGP routes advertised from ATE:port3 must have 2 next hops pointing to tunnels. -* For verifying prefix, nexthop groups, next hop use the leaves mentioed in the path section. -* Verify afts prefix advertised by BGP,ISIS. -* Verify its next hop group, number of next hop and its interfaces. -* Verify the number of next hop is same as expected. - -## AFT-1.1.3: AFT Base Link Up scenario 1 - -### Procedure - -Bring up link between ATE:port1 and DUT:port1 using OTG api. - -### Verifications - -* BGP routes advertised from ATE:port1,port2 must have one next hop. -* IS-IS routes advertised from ATE:port3 must have one next hop. -* BGP routes advertised from ATE:port3 must have 2 next hops pointing to tunnels. -* Verify afts prefix advertised by BGP,ISIS. -* For verifying prefix, nexthop groups, next hop use the leaves mentioed in the path section. -* Verify its next hop group, number of next hop and its interfaces. -* Verify the number of next hop is same as expected. - -## AFT-1.1.4: AFT Base Link Up scenario 2 - -### Procedure - -Bring up both link between ATE:port1,port2 and DUT:port1,port2 using OTG api. - -### Verifications - -* BGP routes advertised from ATE:port1,port2 must have 2 next hops. -* IS-IS routes advertised from ATE:port3 must have one next hop. -* BGP routes advertised from ATE:port3 must have 2 next hops pointing to tunnels. -* For verifying prefix, nexthop groups, next hop use the leaves mentioed in the path section. -* Verify afts prefix advertised by BGP,ISIS. -* Verify its next hop group, number of next hop and its interfaces. -* Verify the number of next hop is same as expected. - -## 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 ## - - - ## State Paths ## - - /network-instances/network-instance/afts/ethernet/mac-entry/state/next-hop-group: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix: - /network-instances/network-instance/afts/aft-summaries/ipv4-unicast/protocols/protocol/state/origin-protocol: - /network-instances/network-instance/afts/aft-summaries/ipv6-unicast/protocols/protocol/state/origin-protocol: - /network-instances/network-instance/afts/next-hop-groups/next-hop-group/id: - /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/index: - /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index: - /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: - /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/backup-next-hop-group: - /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: - /network-instances/network-instance/afts/next-hops/next-hop/index: - /network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/interface: - /network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/subinterface: - /network-instances/network-instance/afts/next-hops/next-hop/state/encapsulate-header: - /network-instances/network-instance/afts/next-hops/next-hop/state/index: - /network-instances/network-instance/afts/next-hops/next-hop/state/ip-address: - /network-instances/network-instance/afts/next-hops/next-hop/state/mac-address: - /network-instances/network-instance/afts/next-hops/next-hop/state/origin-protocol: - /network-instances/network-instance/afts/state-synced/state/ipv4-unicast: - /network-instances/network-instance/afts/state-synced/state/ipv6-unicast: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/entry-metadata: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group-network-instance: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-network-instance: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/entry-metadata: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group-network-instance: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-network-instance: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/prefix: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/prefix: - -rpcs: - gnmi: - gNMI.Subscribe: -``` - -## Control Protocol Coverage - -BGP -IS-IS -RSVP -MPLS - -## Minimum DUT Platform Requirement - -vRX diff --git a/feature/aft/aft_base/otg_tests/afts_prefix_counters/README.md b/feature/aft/aft_base/otg_tests/afts_prefix_counters/README.md deleted file mode 100644 index 9fce154e4a1..00000000000 --- a/feature/aft/aft_base/otg_tests/afts_prefix_counters/README.md +++ /dev/null @@ -1,178 +0,0 @@ -# AFT-2.1: AFTs Prefix Counters - -## Summary - -IPv4/IPv6 prefix counters - -## Testbed - -* atedut_2.testbed - -## Test Setup - -### Generate DUT and ATE Configuration - -Configure DUT:port1 for IS-IS session with ATE:port1 -* IS-IS must be level 2 only with wide metric. -* IS-IS must be point to point. -* Send 1000 ipv4 and 1000 ipv6 IS-IS prefixes from ATE:port1 to DUT:port1. - -Establish eBGP sessions between ATE:port1 and DUT:port1. -* Configure eBGP over the interface ip. -* Advertise 1000 ipv4,ipv6 prefixes from ATE port1 observe received prefixes at DUT. - -### Procedure - -* Gnmi set with REPLACE option to push the configuration DUT. -* ATE configuration must be pushed. - -### verifications - -* BGP routes advertised from ATE:port1 must have 1 nexthop. -* IS-IS routes advertised from ATE:port1 must have one next hop. -* Use gnmi Subscribe with ON_CHANGE option to /network-instances/network-instance/afts. -* Verify afts prefix entries using the following paths with in a timeout of 30s. - -/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix, -/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix - - - -## AFT-2.1.1: AFT Prefix Counters ipv4 packets forwarded, ipv4 octets forwarded IS-IS route. - -### Procedure - -From ATE:port2 send 10000 packets to one of the ipv4 prefix advertise by IS-IS. - -### Verifications - -* Before the traffic measure the initial counter value. -* After the traffic measure the final counter value. -* The difference between final and initial value must match with the counter value in ATE then the test is marked as passed. -* Verify afts ipv4 forwarded packets and ipv4 forwarded octets counter entries using the path mentioned in the paths section of this test plan. - -## AFT-2.1.2: AFT Prefix Counters ipv4 packets forwarded, ipv4 octets forwarded BGP route. - -### Procedure - -From ATE:port2 send 10000 packets to one of the ipv4 prefix advertise by BGP. - -### Verifications - -* Before the traffic measure the initial counter value. -* After the traffic measure the final counter value. -* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. -* Verify afts ipv4 forwarded packets and ipv4 forwarded octets counter entries using the path mentioned in the paths section of this test plan. - - -## AFT-2.1.3: AFT Prefix Counters ipv6 packets forwarded, ipv6 octets forwarded IS-IS route. - -### Procedure - -From ATE:port2 send 10000 packets to one of the ipv6 prefix advertise by IS-IS. - -### Verifications - -* Before the traffic measure the initial counter value. -* After the traffic measure the final counter value. -* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. -* Verify afts ipv6 forwarded packets and ipv6 forwarded octets counter entries using the path mentioned in the paths section of this test plan. - -## AFT-2.1.4: AFT Prefix Counters ipv6 packets forwarded, ipv6 octets forwarded BGP route. - -### Procedure - -From ATE:port2 send 10000 packets to one of the ipv6 prefix advertise by BGP. - -### Verifications - -* Before the traffic measure the initial counter value. -* After the traffic measure the final counter value. -* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. -* Verify afts ipv6 forwarded packets and ipv6 forwarded octets counter entries using the path mentioned in the paths section of this test plan. - -## AFT-2.1.5: AFT Prefix Counters withdraw the ipv4 prefix. - -### Procedure - -* From ATE:port1 withdraw some prefixes of BGP and IS-IS. -* Send 10000 packets from ATE:port2 to DUT:port2 for one of the withdrawn ipv4 prefix. -* The traffic must blackhole. - -### Verifications - -* The counters must not send incremental value as the prefix is not present in RIB/FIB. The test fails if the counter shows incremental values. -* Verify afts ipv4 forwarded packet counter entries using the path mentioned in the paths section of this test plan. - -## AFT-2.1.6: AFT Prefix Counters add the ipv4 prefix back. - -### Procedure - -* From ATE:port1 add the prefixes of BGP and IS-IS back. -* Send 10000 packets from ATE:port2 to DUT:port2 for one of the added ipv4 prefix. -* The traffic must flow end to end. - -### Verifications - -* Before the traffic measure the initial counter value. -* After the traffic measure the final counter value. -* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. -* Verify afts counter entries using the path mentioned in the paths section of this test plan. - -## AFT-2.1.7: AFT Prefix Counters withdraw the ipv6 prefix. - -### Procedure - -* From ATE:port1 withdraw some prefixes of BGP and IS-IS. -* Send 10000 packets from ATE:port2 to DUT:port2 for one of the withdrawn ipv6 prefix. -* The traffic must blackhole. - -### Verifications - -* The counters must not send incremental value as the prefix is not present in RIB/FIB. The test fails if the counter shows incremental values. -* Verify afts counter entries using the path mentioned in the paths section of this test plan. - -## AFT-2.1.8: AFT Prefix Counters add the ipv6 prefix back. - -### Procedure - -* From ATE:port1 add the prefixes of BGP and IS-IS back. -* Send 10000 packets from ATE:port2 to DUT:port2 for one of the added ipv6 prefix. -* The traffic must flow end to end. - -### Verifications - -* Before the traffic measure the initial counter value. -* After the traffic measure the final counter value. -* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. -* Verify afts counter entries using the path mentioned in the paths section of this test plan. - -## 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 ## - - ## State Paths ## - - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded: - /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded: - /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded: - - -rpcs: - gnmi: - gNMI.Subscribe: -``` - -## Control Protocol Coverage - -BGP -IS-IS - -## Minimum DUT Platform Requirement - -vRX \ No newline at end of file diff --git a/feature/aft/feature.textproto b/feature/aft/feature.textproto deleted file mode 100644 index b8ec741be44..00000000000 --- a/feature/aft/feature.textproto +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "aft" - version: 1 -} - -telemetry_path { - path: "/network-instances/network-instance/afts/ethernet/mac-entry/state/next-hop-group" -} - -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group" -} -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded" -} -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded" -} -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol" -} -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix" -} - -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/id" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/backup-next-hop-group" -} - -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/encapsulate-header" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/ip-address" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/mac-address" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/origin-protocol" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/encapsulate-header" -} - -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/interface" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/subinterface" -} - -feature_profile_dependency { - name: "networkinstance" - version: 1 -} diff --git a/feature/afts/otg_tests/afts_atomic/README.md b/feature/afts/otg_tests/afts_atomic/README.md new file mode 100644 index 00000000000..655c49481aa --- /dev/null +++ b/feature/afts/otg_tests/afts_atomic/README.md @@ -0,0 +1,440 @@ +# AFT-3.1: AFTs Atomic Flag Check + +## Summary + +Check the atomic flag in the notification. + +## Testbed + +* featureprofiles/topologies/atedut_2.testbed + +## Test Setup + +### Generate DUT and ATE Configuration + +Variables + +* Let `X` be the number of IPv4 prefixes to be advertised by eBGP. **(User Adjustable Value)** +* Let `Y` be the number of IPv6 prefixes to be advertised by eBGP. **(User Adjustable Value)** +* Let `Z` be the number of IPv4 prefixes to be advertised by IS-IS. **(User Adjustable Value)** +* Let `Z1` be the number of IPv6 prefixes to be advertised by IS-IS. **(User Adjustable Value)** + +Configure IS-IS session. + +* Configure DUT:port1,port2 for IS-IS session with ATE:port1,port2. +* IS-IS must be level 2 only with wide metric. +* IS-IS must be point to point. +* Send `Z` IPv4 and `Z1` IPv6 prefixes from ATE:port1 to DUT:port1. +* Each prefix advertised by ISIS must have one next hop pointing to ATE port1. + +Configure eBGP multipath sessions. + +* Configure eBGP over the interface IP between ATE:port1,port2 and DUT:port1,port2. +* eBGP DUT AS is 65501 and peer AS is 200. +* eBGP is enabled for address family IPv4 and IPv6. +* Advertise `X` IPv4 and `Y` IPv6 prefixes from ATE port1,port2. +* Each prefix advertised by eBGP must have 2 next hops pointing to ATE port1 and ATE port2. + +### Procedure + +* Use gNMI.UPDATE option to push the Test Setup configuration to the DUT. +* ATE configuration must be pushed. + +### Verifications + +* eBGP must be established state. +* Use gNMI Subscribe with `ON_CHANGE` option to `/network-instances/network-instance/afts`. +* Verify all the notifcations send from the DUT for the paths mentioned in the path section must have atomic flag set to true. + + +## AFT-3.1.1: AFT Atomic Flag check scenario 1 + +### Procedure + +Bring down the link between ATE:port2 and DUT:port2 using OTG API. + +### Verifications + +* Verify all the notifcations send from the DUT for the paths mentioned in the path section must have atomic flag set to true. + +## AFT-3.1.2: AFT Atomic Flag Check Link Down and Up scenario 2 + +### Procedure + +Bring down both links between ATE:port1,port2 and DUT:port1,port2 using OTG API and bring up using OTG API. + +### Verifications + +* eBGP routes advertised from ATE:port1,port2 must be removed from RIB and FIB of the DUT (query results should be nil). +* ISIS routes advertised from ATE:port1 must be removed from RIB and FIB of the DUT (query result should be nil). +* Bring up the both links using OTG API. +* Verify eBGP must be in established state for both peers. +* Verify all the notifcations send from the DUT for the paths mentioned in the path section must have atomic flag set to 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: + ## State Paths ## + + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: + /network-instances/network-instance/afts/next-hops/next-hop/state/index: + +rpcs: + gnmi: + gNMI.Subscribe: +``` +## Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "BGP", + "name": "BGP", + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "bgp": { + "global": { + "config": { + "as": 65501, + "router-id": "192.0.2.1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + } + ] + } + }, + "peer-groups": { + "peer-group": [ + { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + } + ] + }, + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.0.2.2", + "config": { + "neighbor-address": "192.0.2.2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::2", + "config": { + "neighbor-address": "2001:db8::2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "192.0.2.6", + "config": { + "neighbor-address": "192.0.2.6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::6", + "config": { + "neighbor-address": "2001:db8::6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + } + ] + } + } + }, + { + "identifier": "ISIS", + "name": "ISIS", + "config": { + "identifier": "ISIS", + "name": "ISIS" + }, + "isis": { + "global": { + "config": { + "hello-padding": "DISABLE" + } + }, + "interfaces": { + "interface": [ + { + "interface-id": "port1", + "config": { + "interface-id": "port1", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + }, + { + "interface-id": "port2", + "config": { + "interface-id": "port2", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + } + ] + } + } + } + ] + } + } + ] + }, + "routing-policy": { + "policy-definitions": { + "policy-definition": [ + { + "name": "ALLOW", + "config": { + "name": "ALLOW" + }, + "statements": { + "statement": [ + { + "name": "20", + "config": { + "name": "20" + }, + "actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + } + } + ] + } + } + ] + } + } +} +``` \ No newline at end of file diff --git a/feature/afts/otg_tests/afts_atomic/metadata.textproto b/feature/afts/otg_tests/afts_atomic/metadata.textproto new file mode 100644 index 00000000000..77eb1d09c85 --- /dev/null +++ b/feature/afts/otg_tests/afts_atomic/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "8e46e4ea-e114-40e7-9b96-2b7e8337a709" +plan_id: "AFT-3.1" +description: "AFTs Atomic Flag Check" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/afts/otg_tests/afts_base/README.md b/feature/afts/otg_tests/afts_base/README.md new file mode 100644 index 00000000000..8d8ff92d8bc --- /dev/null +++ b/feature/afts/otg_tests/afts_base/README.md @@ -0,0 +1,472 @@ +# AFT-1.1: AFTs Base + +## Summary + +IPv4/IPv6 unicast routes next hop group and next hop. + +## Testbed + +* atedut_2.testbed + +## Test Setup + +### Generate DUT and ATE Configuration + +Configure DUT:port1,port2 for IS-IS session with ATE:port1,port2. + +* Let `X` be the number of IPv4 prefixes to be advertised by eBGP. **(User Adjustable Value)** +* Let `Y` be the number of IPv6 prefixes to be advertised by eBGP. **(User Adjustable Value)** +* Let `Z` be the number of prefixes to be advertised by IS-IS. **(User Adjustable Value)** +* IS-IS must be level 2 only with wide metric. +* IS-IS must be point to point. +* Send `Z` IPv4 and `Z` IPv6 prefixes from ATE:port1 to DUT:port1. + +Establish eBGP multipath sessions between ATE:port1,port2 and DUT:port1,port2 + +* Configure eBGP over the interface IP between ATE:port1,port2 and DUT:port1,port2. +* Advertise `X` IPv4 and `Y` IPv6 prefixes from ATE port1,port2. +* Each prefix advertised by eBGP must have 2 next hops pointing to ATE port1 and ATE port2. +* Each prefix advertised by ISIS must have one next hop pointing to ATE port1. + +### Procedure + +* Use gNMI.UPDATE option to push the Test Setup configuration to the DUT. +* ATE configuration must be pushed. + +### Verifications + +* eBGP routes advertised from ATE:port1,port2 must have 2 nexthops. +* Use gNMI Subscribe with `ON_CHANGE` option to `/network-instances/network-instance/afts`. +* Verify AFTs prefixes advertised by eBGP and ISIS. +* Verify their next hop group, number of next hops, and the name of the interfaces. +* Verify the number of next hops is 2 for eBGP advertised prefixes. +* Verify the number of next hop is 1 for ISIS advertised prefixes. +* Verify the prefixes are pointing to the correct egress interface(s). +* Verify all other leaves mentioned in the path section have the data populated correctly. + +## AFT-1.1.1: AFT Base Link Down scenario 1 + +### Procedure + +Bring down the link between ATE:port2 and DUT:port2 using OTG API. + +### Verifications + +* eBGP routes advertised from ATE:port1,port2 must have 1 nexthop (pointing to ATE:port1). +* IS-IS routes advertised from ATE:port1 must have one next hop. +* Verify AFTs prefixes advertised by eBGP and ISIS. +* Verify their next hop group, number of next hops, and the name of the interfaces. +* Verify the number of next hop per prefix must be 1. + +## AFT-1.1.2: AFT Base Link Down scenario 2 + +### Procedure + +Bring down both links between ATE:port1,port2 and DUT:port1,port2 using OTG API. + +### Verifications + +* eBGP routes advertised from ATE:port1,port2 must be removed from RIB and FIB of the DUT (query results should be nil). +* ISIS routes advertised from ATE:port1 must be removed from RIB and FIB of the DUT (query result should be nil). + +## AFT-1.1.3: AFT Base Link Up scenario 3 + +### Procedure + +Bring up the link between ATE:port1 and DUT:port1 using OTG API. + +### Verifications + +* eBGP routes advertised from ATE:port1,port2 must have one next hop (pointing to ATE:port1). +* IS-IS routes advertised from ATE:port1 must have one next hop. +* Verify AFTs prefixes advertised by eBGP and ISIS. +* Verify their next hop group, number of next hops, and the name of the interfaces. +* Verify the number of next hop per prefix is 1. + +## AFT-1.1.4: AFT Base Link Up scenario 4 + +### Procedure + +Bring up both links between ATE:port1,port2 and DUT:port1,port2 using OTG API. + +### Verifications + +* eBGP routes advertised from ATE:port1,port2 must have 2 next hops. +* IS-IS routes advertised from ATE:port1 must have one next hop. +* Verify AFTs prefixes advertised by eBGP and ISIS. +* Verify their next hop group, number of next hops, and the name of the interfaces. + + +## 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: + ## State Paths ## + + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: + /network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/interface: + /network-instances/network-instance/afts/next-hops/next-hop/state/index: + /network-instances/network-instance/afts/next-hops/next-hop/state/ip-address: + +rpcs: + gnmi: + gNMI.Subscribe: +``` +## Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "BGP", + "name": "BGP", + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "bgp": { + "global": { + "config": { + "as": 65501, + "router-id": "192.0.2.1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + } + ] + } + }, + "peer-groups": { + "peer-group": [ + { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + } + ] + }, + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.0.2.2", + "config": { + "neighbor-address": "192.0.2.2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::2", + "config": { + "neighbor-address": "2001:db8::2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "192.0.2.6", + "config": { + "neighbor-address": "192.0.2.6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::6", + "config": { + "neighbor-address": "2001:db8::6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + } + ] + } + } + }, + { + "identifier": "ISIS", + "name": "ISIS", + "config": { + "identifier": "ISIS", + "name": "ISIS" + }, + "isis": { + "global": { + "config": { + "hello-padding": "DISABLE" + } + }, + "interfaces": { + "interface": [ + { + "interface-id": "port1", + "config": { + "interface-id": "port1", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + }, + { + "interface-id": "port2", + "config": { + "interface-id": "port2", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + } + ] + } + } + } + ] + } + } + ] + }, + "routing-policy": { + "policy-definitions": { + "policy-definition": [ + { + "name": "ALLOW", + "config": { + "name": "ALLOW" + }, + "statements": { + "statement": [ + { + "name": "20", + "config": { + "name": "20" + }, + "actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + } + } + ] + } + } + ] + } + } +} +``` diff --git a/feature/afts/otg_tests/afts_base/afts_base_test.go b/feature/afts/otg_tests/afts_base/afts_base_test.go new file mode 100644 index 00000000000..c409e3bbcfe --- /dev/null +++ b/feature/afts/otg_tests/afts_base/afts_base_test.go @@ -0,0 +1,643 @@ +// Copyright 2025 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 afts_base_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/isissession" + "github.com/openconfig/featureprofiles/internal/telemetry/aftcache" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygnmi/ygnmi" + + gnmipb "github.com/openconfig/gnmi/proto/gnmi" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + advertisedRoutesV4Prefix = 32 + advertisedRoutesV6Prefix = 128 + dutAS = 65501 + ateAS = 200 + v4PrefixLen = 30 + v6PrefixLen = 126 + mtu = 1500 + isisSystemID = "650000000001" + applyPolicyType = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + applyPolicyName = "ALLOW" + peerGrpNameV4P1 = "BGP-PEER-GROUP-V4-P1" + peerGrpNameV6P1 = "BGP-PEER-GROUP-V6-P1" + peerGrpNameV4P2 = "BGP-PEER-GROUP-V4-P2" + peerGrpNameV6P2 = "BGP-PEER-GROUP-V6-P2" + port1MAC = "00:00:02:02:02:02" + port2MAC = "00:00:03:03:03:03" + bgpRoute = "200.0.0.0" + bgpRoutev6 = "3001:1::0" + startingBGPRouteIPv4 = "200.0.0.0/32" + startingBGPRouteIPv6 = "3001:1::0/128" + isisRouteCount = 100 + isisRoute = "199.0.0.1" + isisRoutev6 = "2001:db8::203:0:113:1" + startingISISRouteIPv4 = "199.0.0.1/32" + startingISISRouteIPv6 = "2001:db8::203:0:113:1/128" + aftConvergenceTime = 20 * time.Minute + bgpTimeout = 2 * time.Minute + linkLocalAddress = "fe80::200:2ff:fe02:202" + bgpRouteCountIPv4LowScale = 100000 + bgpRouteCountIPv6LowScale = 100000 + bgpRouteCountIPv4Default = 2000000 + bgpRouteCountIPv6Default = 1000000 +) + +var ( + dutP1 = attrs.Attributes{ + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: v4PrefixLen, + IPv6Len: v6PrefixLen, + } + ateP1 = attrs.Attributes{ + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: v4PrefixLen, + IPv6Len: v6PrefixLen, + } + dutP2 = attrs.Attributes{ + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: v4PrefixLen, + IPv6Len: v6PrefixLen, + } + ateP2 = attrs.Attributes{ + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: v4PrefixLen, + IPv6Len: v6PrefixLen, + } + wantIPv4NHs = map[string]bool{ateP1.IPv4: true, ateP2.IPv4: true} + wantIPv6NHs = map[string]bool{ateP1.IPv6: true, ateP2.IPv6: true} + wantIPv4NHsPostChurn = map[string]bool{ateP1.IPv4: true} + port1Name = "port1" + port2Name = "port2" +) + +// getRouteCount returns the expected route count for the given dut and IP family. +func getRouteCount(dut *ondatra.DUTDevice, afi IPFamily) uint32 { + if deviations.LowScaleAft(dut) { + if afi == IPv4 { + return bgpRouteCountIPv4LowScale + } + return bgpRouteCountIPv6LowScale + } + if afi == IPv4 { + return bgpRouteCountIPv4Default + } + return bgpRouteCountIPv6Default +} + +// getPostChurnIPv6NH returns the expected IPv6 next hops after a churn event. +// It returns a map of IP addresses to a boolean indicating if the address is expected. +func getPostChurnIPv6NH(dut *ondatra.DUTDevice) map[string]bool { + if deviations.LinkLocalInsteadOfNh(dut) { + return map[string]bool{linkLocalAddress: true} + } + return map[string]bool{ateP1.IPv6: true} +} + +// configureDUT configures all the interfaces and BGP on the DUT. +func (tc *testCase) configureDUT(t *testing.T) error { + dut := tc.dut + p1 := dut.Port(t, port1Name).Name() + i1 := dutP1.NewOCInterface(p1, dut) + gnmi.Update(t, dut, gnmi.OC().Interface(p1).Config(), i1) + + p2 := dut.Port(t, port2Name).Name() + i2 := dutP2.NewOCInterface(p2, dut) + gnmi.Update(t, dut, gnmi.OC().Interface(p2).Config(), i2) + + // Configure Network instance type on DUT. + t.Log("Configure/update Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, port1Name)) + fptest.SetPortSpeed(t, dut.Port(t, port2Name)) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1, deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2, deviations.DefaultNetworkInstance(dut), 0) + } + + d := &oc.Root{} + routePolicy := d.GetOrCreateRoutingPolicy() + policyDefinition := routePolicy.GetOrCreatePolicyDefinition(applyPolicyName) + statement, err := policyDefinition.AppendNewStatement("id-1") + if err != nil { + return fmt.Errorf("failed to append new statement to policy definition %s: %v", applyPolicyName, err) + } + statement.GetOrCreateActions().PolicyResult = applyPolicyType + gnmi.Update(t, dut, gnmi.OC().RoutingPolicy().Config(), routePolicy) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + nbrs := []*BGPNeighbor{ + {as: ateAS, neighborip: ateP1.IPv4, version: IPv4}, + {as: ateAS, neighborip: ateP1.IPv6, version: IPv6}, + } + dutConf := createBGPNeighbor(peerGrpNameV4P1, peerGrpNameV6P1, nbrs, dut) + gnmi.Update(t, dut, dutConfPath.Config(), dutConf) + if deviations.BGPMissingOCMaxPrefixesConfiguration(dut) { + updateNeighborMaxPrefix(t, dut, nbrs) + } + nbrs = []*BGPNeighbor{ + {as: ateAS, neighborip: ateP2.IPv4, version: IPv4}, + {as: ateAS, neighborip: ateP2.IPv6, version: IPv6}, + } + dutConf = createBGPNeighbor(peerGrpNameV4P2, peerGrpNameV6P2, nbrs, dut) + gnmi.Update(t, dut, dutConfPath.Config(), dutConf) + if deviations.BGPMissingOCMaxPrefixesConfiguration(dut) { + updateNeighborMaxPrefix(t, dut, nbrs) + } + + ts := isissession.MustNew(t).WithISIS() + ts.ConfigISIS(func(isis *oc.NetworkInstance_Protocol_Isis) { + global := isis.GetOrCreateGlobal() + global.HelloPadding = oc.Isis_HelloPaddingType_DISABLE + + if deviations.ISISSingleTopologyRequired(ts.DUT) { + afv6 := global.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST) + afv6.GetOrCreateMultiTopology().SetAfiName(oc.IsisTypes_AFI_TYPE_IPV4) + afv6.GetOrCreateMultiTopology().SetSafiName(oc.IsisTypes_SAFI_TYPE_UNICAST) + } + }) + ts.ATEIntf1.Isis().Advanced().SetEnableHelloPadding(false) + + if err := ts.PushAndStart(t); err != nil { + return err + } + + if _, err = ts.AwaitAdjacency(); err != nil { + return fmt.Errorf("no IS-IS adjacency formed: %v", err) + } + return nil +} + +type BGPNeighbor struct { + as uint32 + neighborip string + version IPFamily +} +type IPFamily int + +const ( + // UnknownIPFamily indicates an unspecified or unknown IP address family. + UnknownIPFamily IPFamily = iota + IPv4 + IPv6 +) + +func createBGPNeighbor(peerGrpNameV4, peerGrpNameV6 string, nbrs []*BGPNeighbor, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProtocol := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProtocol.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.SetAs(dutAS) + global.SetRouterId(dutP1.IPv4) + + // Note: we have to define the peer group even if we aren't setting any policy because it's + // invalid OC for the neighbor to be part of a peer group that doesn't exist. + peerGroupV4 := bgp.GetOrCreatePeerGroup(peerGrpNameV4) + peerGroupV4.SetPeerAs(ateAS) + peerGroupV6 := bgp.GetOrCreatePeerGroup(peerGrpNameV6) + peerGroupV6.SetPeerAs(ateAS) + + afiSAFI := global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afiSAFI.SetEnabled(true) + asisafi6 := global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + asisafi6.SetEnabled(true) + + peerGroupV4AfiSafi := peerGroupV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + peerGroupV4AfiSafi.SetEnabled(true) + peerGroupV6AfiSafi := peerGroupV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + peerGroupV6AfiSafi.SetEnabled(true) + + if deviations.MultipathUnsupportedNeighborOrAfisafi(dut) { + peerGroupV4.GetOrCreateUseMultiplePaths().SetEnabled(true) + peerGroupV6.GetOrCreateUseMultiplePaths().SetEnabled(true) + } else { + afiSAFI.GetOrCreateUseMultiplePaths().GetOrCreateEbgp().SetMaximumPaths(2) + asisafi6.GetOrCreateUseMultiplePaths().GetOrCreateEbgp().SetMaximumPaths(2) + peerGroupV4AfiSafi.GetOrCreateUseMultiplePaths().SetEnabled(true) + peerGroupV6AfiSafi.GetOrCreateUseMultiplePaths().SetEnabled(true) + } + for _, nbr := range nbrs { + neighbor := bgp.GetOrCreateNeighbor(nbr.neighborip) + neighbor.SetPeerAs(nbr.as) + neighbor.SetEnabled(true) + switch nbr.version { + case IPv4: + neighbor.SetPeerGroup(peerGrpNameV4) + neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).SetEnabled(true) + neighbourAFV4 := peerGroupV4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + neighbourAFV4.SetEnabled(true) + applyPolicy := neighbourAFV4.GetOrCreateApplyPolicy() + applyPolicy.ImportPolicy = []string{applyPolicyName} + applyPolicy.ExportPolicy = []string{applyPolicyName} + case IPv6: + neighbor.SetPeerGroup(peerGrpNameV6) + neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).SetEnabled(true) + neighbourAFV6 := peerGroupV6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + neighbourAFV6.SetEnabled(true) + applyPolicy := neighbourAFV6.GetOrCreateApplyPolicy() + applyPolicy.ImportPolicy = []string{applyPolicyName} + applyPolicy.ExportPolicy = []string{applyPolicyName} + } + } + return niProtocol +} + +func updateNeighborMaxPrefix(t *testing.T, dut *ondatra.DUTDevice, neighbors []*BGPNeighbor) { + for _, nbr := range neighbors { + cfgplugins.DeviationAristaBGPNeighborMaxPrefixes(t, dut, nbr.neighborip, 0) + } +} + +func (tc *testCase) waitForBGPSession(t *testing.T) error { + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(tc.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + nbrPath := statePath.Neighbor(ateP2.IPv4) + nbrPathv6 := statePath.Neighbor(ateP2.IPv6) + verifySessionState := func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if !ok { + t.Logf("BGP session state not found for neighbor %s", val.Path.String()) + return false + } + t.Logf("BGP session state for neighbor %s: %s", val.Path.String(), state.String()) + return state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + } + + _, ok := gnmi.Watch(t, tc.dut, nbrPath.SessionState().State(), bgpTimeout, verifySessionState).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, tc.dut, nbrPath.State())) + return fmt.Errorf("BGP session with %s not established", ateP2.IPv4) + } + _, ok = gnmi.Watch(t, tc.dut, nbrPathv6.SessionState().State(), bgpTimeout, verifySessionState).Await(t) + if !ok { + fptest.LogQuery(t, "BGPv6 reported state", nbrPathv6.State(), gnmi.Get(t, tc.dut, nbrPathv6.State())) + return fmt.Errorf("BGP session with %s not established", ateP2.IPv6) + } + return nil +} + +func (tc *testCase) configureATE(t *testing.T) { + ate := tc.ate + ap1 := ate.Port(t, port1Name) + ap2 := ate.Port(t, port2Name) + config := gosnappi.NewConfig() + // add ports + p1 := config.Ports().Add().SetName(ap1.ID()) + p2 := config.Ports().Add().SetName(ap2.ID()) + // add devices + d1 := config.Devices().Add().SetName(p1.Name() + ".d1") + d2 := config.Devices().Add().SetName(p2.Name() + ".d2") + + // Configuration on port1. + d1Eth := d1.Ethernets(). + Add(). + SetName(d1.Name() + ".eth"). + SetMac(port1MAC). + SetMtu(mtu) + d1Eth. + Connection(). + SetPortName(p1.Name()) + + d1IPv4 := d1Eth. + Ipv4Addresses(). + Add(). + SetName(d1Eth.Name() + ".IPv4"). + SetAddress(ateP1.IPv4). + SetGateway(dutP1.IPv4). + SetPrefix(v4PrefixLen) + + d1IPv6 := d1Eth. + Ipv6Addresses(). + Add(). + SetName(d1Eth.Name() + ".IPv6"). + SetAddress(ateP1.IPv6). + SetGateway(dutP1.IPv6). + SetPrefix(v6PrefixLen) + + d1ISIS := d1.Isis(). + SetName(d1.Name() + ".isis"). + SetSystemId(isisSystemID) + d1ISIS.Basic(). + SetIpv4TeRouterId(d1IPv4.Address()). + SetHostname("ixia-c-port1") + d1ISIS.Advanced().SetAreaAddresses([]string{"49"}) + d1ISISInt := d1ISIS.Interfaces(). + Add(). + SetName(d1ISIS.Name() + ".intf"). + SetEthName(d1Eth.Name()). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetMetric(10) + d1ISISInt.TrafficEngineering().Add().PriorityBandwidths() + d1ISISInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + d1ISISRoute := d1ISIS.V4Routes().Add().SetName(d1ISIS.Name() + ".rr") + d1ISISRoute.Addresses(). + Add(). + SetAddress(isisRoute). + SetPrefix(advertisedRoutesV4Prefix).SetCount(isisRouteCount) + + d1ISISRouteV6 := d1ISIS.V6Routes().Add().SetName(d1ISISRoute.Name() + ".v6") + d1ISISRouteV6.Addresses(). + Add(). + SetAddress(isisRoutev6). + SetPrefix(advertisedRoutesV6Prefix).SetCount(isisRouteCount) + + tc.configureBGPDev(d1, d1IPv4, d1IPv6) + + // Configuration on port2 + d2Eth := d2.Ethernets(). + Add(). + SetName(d2.Name() + ".eth"). + SetMac(port2MAC). + SetMtu(mtu) + d2Eth. + Connection(). + SetPortName(p2.Name()) + d2IPv4 := d2Eth.Ipv4Addresses(). + Add(). + SetName(d2Eth.Name() + ".IPv4"). + SetAddress(ateP2.IPv4). + SetGateway(dutP2.IPv4). + SetPrefix(v4PrefixLen) + + d2IPv6 := d2Eth. + Ipv6Addresses(). + Add(). + SetName(d2Eth.Name() + ".IPv6"). + SetAddress(ateP2.IPv6). + SetGateway(dutP2.IPv6). + SetPrefix(v6PrefixLen) + + d2ISIS := d2.Isis(). + SetName(d2.Name() + ".isis"). + SetSystemId(isisSystemID) + d2ISIS.Basic(). + SetIpv4TeRouterId(d2IPv4.Address()). + SetHostname("ixia-c-port2") + d2ISIS.Advanced().SetAreaAddresses([]string{"49"}) + d2ISISInt := d2ISIS.Interfaces(). + Add(). + SetName(d2ISIS.Name() + ".intf"). + SetEthName(d2Eth.Name()). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_2). + SetMetric(10) + d2ISISInt.TrafficEngineering().Add().PriorityBandwidths() + d2ISISInt.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + d2ISISRoute := d2ISIS.V4Routes().Add().SetName(d2ISIS.Name() + ".rr") + d2ISISRoute.Addresses(). + Add(). + SetAddress(isisRoute). + SetPrefix(advertisedRoutesV4Prefix). + SetCount(isisRouteCount) + + d2ISISRouteV6 := d2ISIS.V6Routes().Add().SetName(d2ISISRoute.Name() + ".v6") + d2ISISRouteV6.Addresses(). + Add(). + SetAddress(isisRoutev6). + SetPrefix(advertisedRoutesV6Prefix). + SetCount(isisRouteCount) + + tc.configureBGPDev(d2, d2IPv4, d2IPv6) + + ate.OTG().PushConfig(t, config) + ate.OTG().StartProtocols(t) +} + +func (tc *testCase) configureBGPDev(dev gosnappi.Device, ipv4 gosnappi.DeviceIpv4, ipv6 gosnappi.DeviceIpv6) { + bgp := dev.Bgp().SetRouterId(ipv4.Address()) + bgp4Peer := bgp.Ipv4Interfaces().Add().SetIpv4Name(ipv4.Name()).Peers().Add().SetName(dev.Name() + ".BGP4.peer") + bgp4Peer.SetPeerAddress(ipv4.Gateway()).SetAsNumber(uint32(ateAS)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgp6Peer := bgp.Ipv6Interfaces().Add().SetIpv6Name(ipv6.Name()).Peers().Add().SetName(dev.Name() + ".BGP6.peer") + bgp6Peer.SetPeerAddress(ipv6.Gateway()).SetAsNumber(uint32(ateAS)).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + routes := bgp4Peer.V4Routes().Add().SetName(bgp4Peer.Name() + ".v4route") + routes.SetNextHopIpv4Address(ipv4.Address()). + SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4). + SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + routes.Addresses().Add(). + SetAddress(bgpRoute). + SetPrefix(advertisedRoutesV4Prefix). + SetCount(getRouteCount(tc.dut, IPv4)) + + routesV6 := bgp6Peer.V6Routes().Add().SetName(bgp6Peer.Name() + ".v6route") + routesV6.SetNextHopIpv6Address(ipv6.Address()). + SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6). + SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + routesV6.Addresses().Add(). + SetAddress(bgpRoutev6). + SetPrefix(advertisedRoutesV6Prefix). + SetCount(getRouteCount(tc.dut, IPv6)) +} + +func (tc *testCase) generateWantPrefixes(t *testing.T) map[string]bool { + wantPrefixes := make(map[string]bool) + for pfix := range netutil.GenCIDRs(t, startingBGPRouteIPv4, int(getRouteCount(tc.dut, IPv4))) { + wantPrefixes[pfix] = true + } + for pfix6 := range netutil.GenCIDRs(t, startingBGPRouteIPv6, int(getRouteCount(tc.dut, IPv6))) { + wantPrefixes[pfix6] = true + } + return wantPrefixes +} + +func (tc *testCase) verifyPrefixes(t *testing.T, aft *aftcache.AFTData, ip string, routeCount int, wantNHCount int) error { + for pfix := range netutil.GenCIDRs(t, ip, routeCount) { + nhgID, ok := aft.Prefixes[pfix] + + if !ok { + return fmt.Errorf("prefix %s not found in AFT", pfix) + } + nhg, ok := aft.NextHopGroups[nhgID] + if !ok { + return fmt.Errorf("next hop group %d not found in AFT for prefix %s", nhgID, pfix) + } + + if len(nhg.NHIDs) != wantNHCount { + return fmt.Errorf("prefix %s has %d next hops, want %d", pfix, len(nhg.NHIDs), wantNHCount) + } + + var firstWeight uint64 = 0 // Initialize with a value that won't be a valid weight + for i := 0; i < wantNHCount; i++ { + nhID := nhg.NHIDs[i] + nh, ok := aft.NextHops[nhID] + if !ok { + return fmt.Errorf("next hop %d not found in AFT for next-hop group: %d for prefix: %s", nhID, nhgID, pfix) + } + // TODO: - Add check for exact interface name + // TODO: - Remove deviation and add recursive check for interface + if !deviations.SkipInterfaceNameCheck(tc.dut) { + if nh.IntfName == "" { + return fmt.Errorf("next hop interface not found in AFT for next-hop: %d for prefix: %s", nhID, pfix) + } + } + if nh.IP == "" { + return fmt.Errorf("next hop IP not found in AFT for next-hop: %d for prefix: %s", nhID, pfix) + } + weight, ok := nhg.NHWeights[nhID] + if !ok { + return fmt.Errorf("next hop weight not found in AFT for next-hop: %d for prefix: %s", nhID, pfix) + } + if weight <= 0 { + return fmt.Errorf("next hop weight is %d, want > 0 for next-hop: %d for prefix: %s", weight, nhID, pfix) + } + // Check if weights are equal + if firstWeight == 0 { // This is the first next hop, set the reference weight + firstWeight = weight + } else if weight != firstWeight { // Compare with the first encountered weight + return fmt.Errorf("next hop group %d has unequal weights. Expected %d, got %d for next-hop %d for prefix %s", nhgID, firstWeight, weight, nhID, pfix) + } + } + } + return nil +} + +func (tc *testCase) cache(t *testing.T, stoppingCondition aftcache.PeriodicHook) (*aftcache.AFTData, error) { + t.Helper() + streamContext, streamCancel := context.WithCancel(t.Context()) + aftSession := aftcache.NewAFTStreamSession(streamContext, t, tc.gnmiClient, tc.dut) + aftSession.ListenUntil(streamContext, t, aftConvergenceTime, stoppingCondition) + streamCancel() + // Get the AFT from the cache. + aft, err := aftSession.Cache.ToAFT(tc.dut) + if err != nil { + return nil, fmt.Errorf("error getting AFT: %v", err) + } + return aft, nil +} + +func (tc *testCase) otgInterfaceState(t *testing.T, portName string, state gosnappi.StatePortLinkStateEnum) { + portStateAction := gosnappi.NewControlState() + portStateAction.Port().Link().SetPortNames([]string{portName}).SetState(state) + tc.ate.OTG().SetControlState(t, portStateAction) +} + +type testCase struct { + name string + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + gnmiClient gnmipb.GNMIClient +} + +func TestBGP(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + gnmiClient, err := dut.RawAPIs().BindingDUT().DialGNMI(t.Context()) + if err != nil { + t.Fatalf("Failed to dial GNMI: %v", err) + } + tc := &testCase{ + name: "AFT Churn Test With Scale", + dut: dut, + ate: ate, + gnmiClient: gnmiClient, + } + + // Pre-generate all expected prefixes once for efficiency + wantPrefixes := tc.generateWantPrefixes(t) + + // Helper function for verifying AFT state when given prefixes and expected next hops. + verifyAFTState := func(desc string, wantNHCount int, wantV4NHs, wantV6NHs map[string]bool) *aftcache.AFTData { + t.Helper() + t.Log(desc) + stoppingCondition := aftcache.InitialSyncStoppingCondition(t, dut, wantPrefixes, wantV4NHs, wantV6NHs) + aft, err := tc.cache(t, stoppingCondition) + if err != nil { + t.Fatalf("failed to get AFT Cache: %v", err) + } + if err := tc.verifyPrefixes(t, aft, startingBGPRouteIPv4, int(getRouteCount(dut, IPv4)), wantNHCount); err != nil { + t.Errorf("failed to verify IPv4 BGP prefixes: %v", err) + } + if err := tc.verifyPrefixes(t, aft, startingBGPRouteIPv6, int(getRouteCount(dut, IPv6)), wantNHCount); err != nil { + t.Errorf("failed to verify IPv6 BGP prefixes: %v", err) + } + return aft + } + + // --- Test Setup --- + if err := tc.configureDUT(t); err != nil { + t.Fatalf("failed to configure DUT: %v", err) + } + tc.configureATE(t) + + t.Log("Waiting for BGP neighbor to establish...") + if err := tc.waitForBGPSession(t); err != nil { + t.Fatalf("Unable to establish BGP session: %v", err) + } + + // Step 1: Initial state verification (BGP: 2 NHs, ISIS: 1 NH) + aft := verifyAFTState("Initial AFT verification", 2, wantIPv4NHs, wantIPv6NHs) + + // Verify ISIS prefixes are present in AFT. + if err := tc.verifyPrefixes(t, aft, startingISISRouteIPv4, isisRouteCount, 1); err != nil { + t.Errorf("failed to verify IPv4 ISIS prefixes: %v", err) + } + if err := tc.verifyPrefixes(t, aft, startingISISRouteIPv6, isisRouteCount, 1); err != nil { + t.Errorf("failed to verify IPv6 ISIS prefixes: %v", err) + } + t.Log("ISIS verification completed") + + // Step 2: Stop Port2 interface to create Churn (BGP: 1 NH) + t.Log("Stopping Port2 interface to create Churn") + tc.otgInterfaceState(t, port2Name, gosnappi.StatePortLinkState.DOWN) + verifyAFTState("AFT verification after port 2 churn", 1, wantIPv4NHsPostChurn, getPostChurnIPv6NH(tc.dut)) + + // Step 3: Stop Port1 interface to create full Churn (BGP: deletion expected) + t.Log("Stopping Port1 interface to create Churn") + tc.otgInterfaceState(t, port1Name, gosnappi.StatePortLinkState.DOWN) + if _, err := tc.cache(t, aftcache.DeletionStoppingCondition(t, dut, wantPrefixes)); err != nil { + t.Fatalf("failed to get AFT Cache after deletion: %v", err) + } + + // Step 4: Start Port1 interface to remove Churn (BGP: 1 NH - Port2 still down) + t.Log("Starting Port1 interface to remove Churn") + tc.otgInterfaceState(t, port1Name, gosnappi.StatePortLinkState.UP) + verifyAFTState("AFT verification after port 1 up", 1, wantIPv4NHsPostChurn, getPostChurnIPv6NH(tc.dut)) + + // Step 5: Start Port2 interface to remove Churn (BGP: 2 NHs - full recovery) + t.Log("Starting Port2 interface to remove Churn") + tc.otgInterfaceState(t, port2Name, gosnappi.StatePortLinkState.UP) + verifyAFTState("AFT verification after port 2 up", 2, wantIPv4NHs, wantIPv6NHs) +} diff --git a/feature/afts/otg_tests/afts_base/metadata.textproto b/feature/afts/otg_tests/afts_base/metadata.textproto new file mode 100644 index 00000000000..7f169d27846 --- /dev/null +++ b/feature/afts/otg_tests/afts_base/metadata.textproto @@ -0,0 +1,37 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "8c299aab-4df4-4213-8a1e-a7789fef0c60" +plan_id: "AFT-1.1" +description: "AFTs Base" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + isis_single_topology_required: true + skip_interface_name_check: true + link_local_instead_of_nh: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + isis_instance_enabled_required: true + isis_interface_afi_unsupported: true + interface_enabled: true + bgp_missing_oc_max_prefixes_configuration: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + isis_level_enabled: true + multipath_unsupported_neighbor_or_afisafi: true + } +} \ No newline at end of file diff --git a/feature/afts/otg_tests/afts_reboot/README.md b/feature/afts/otg_tests/afts_reboot/README.md new file mode 100644 index 00000000000..eaadad61d4c --- /dev/null +++ b/feature/afts/otg_tests/afts_reboot/README.md @@ -0,0 +1,442 @@ +# AFT-5.1: AFTs DUT Reboot + +## Summary + +This test verifies that eBGP multipath and IS-IS routes are correctly reflected in AFT telemetry and that these routes, along with their next-hop information, are successfully restored after a DUT reboot. + +## Testbed + +* featureprofiles/topologies/atedut_2.testbed + +## Test Setup + +### Generate DUT and ATE Configuration + +Variables + +* Let `X` be the number of IPv4 prefixes to be advertised by eBGP. **(User Adjustable Value)** +* Let `Y` be the number of IPv6 prefixes to be advertised by eBGP. **(User Adjustable Value)** +* Let `Z` be the number of IPv4 prefixes to be advertised by IS-IS. **(User Adjustable Value)** +* Let `Z1` be the number of IPv6 prefixes to be advertised by IS-IS. **(User Adjustable Value)** + +Configure IS-IS session. + +* Configure DUT:port1,port2 for IS-IS session with ATE:port1,port2. +* IS-IS must be level 2 only with wide metric. +* IS-IS must be point to point. +* Send `Z` IPv4 and `Z1` IPv6 prefixes from ATE:port1 to DUT:port1. +* Each prefix advertised by ISIS must have one next hop pointing to ATE port1. + +Configure eBGP multipath sessions. + +* Configure eBGP sessions over the interface IPs between `ATE:port1/port2` and `DUT:port1/port2`. +* eBGP DUT AS is 65501 and peer AS is 200. +* Enable IPv4 and IPv6 address families for BGP. +* From both ATE:port1 and ATE:port2, advertise the same set of `X` IPv4 and `Y` IPv6 prefixes. This setup ensures the DUT learns two next-hops for each prefix, enabling multipath routing. +* Each prefix advertised by eBGP must have 2 next hops pointing to ATE port1 and ATE port2. + +### Procedure + +* Use gNMI UPDATE option to push the Test Setup configuration to the DUT. +* ATE configuration must be pushed. + +### Verifications + +* Using gNMI Subscribe with an ON_CHANGE subscription, confirm that /`network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state` is `ESTABLISHED` for each BGP neighbor. +* Initiate a gNMI Subscribe stream with ON_CHANGE mode to the path `/network-instances/network-instance[name=DEFAULT]/afts/.` +* For each eBGP prefix received, verify that the AFT entry points to a next-hop-group that contains two next-hops. +* For each IS-IS prefix received, verify that the AFT entry points to a next-hop-group that contains one next-hop. +* For all prefixes, verify that the `interface-ref/state/interface` leaf within the next-hop object points to the correct egress interface (e.g., port1, port2). +* Verify that all other leaves within the subscribed AFT paths (e.g., prefix, next-hop-group, id, ip-address) contain valid, non-default data. + +## AFT-5.1.1: AFT DUT Reboot + +### Procedure + +* Initiate a reboot on the DUT using the gNOI.System.Reboot RPC. +* After initiating the reboot, continuously poll the `/system/state/boot-time` leaf via gNMI subscribe once request. Wait for the DUT to respond and for the BGP peerings to re-establish by polling `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state` until it returns to `ESTABLISHED`. + +### Verifications + +* Verify that the value of `/system/state/boot-time` after the reboot is greater than the value read before the reboot. +* During reboot gNMI connection will terminate, retry must be there till the device comes up. +* Confirm that `/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state` is `ESTABLISHED` for all BGP neighbors. +* Using the gNMI Subscribe stream for `/network-instances/network-instance[name=DEFAULT]/afts/`, re-verify the state of the AFTs: +* Confirm eBGP prefixes have next-hop-groups with two next-hops. +* Confirm IS-IS prefixes have next-hop-groups with one next-hop. +* Confirm all next-hops point to the correct egress interfaces. +* Confirm all relevant AFT leaves are populated with the correct post-reboot state. + +## 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: + ## State Paths ## + + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: + /network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/interface: + /network-instances/network-instance/afts/next-hops/next-hop/state/index: + /network-instances/network-instance/afts/next-hops/next-hop/state/ip-address: + +rpcs: + gnmi: + gNMI.Subscribe: +``` +## Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "BGP", + "name": "BGP", + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "bgp": { + "global": { + "config": { + "as": 65501, + "router-id": "192.0.2.1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + } + ] + } + }, + "peer-groups": { + "peer-group": [ + { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + } + ] + }, + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.0.2.2", + "config": { + "neighbor-address": "192.0.2.2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::2", + "config": { + "neighbor-address": "2001:db8::2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "192.0.2.6", + "config": { + "neighbor-address": "192.0.2.6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::6", + "config": { + "neighbor-address": "2001:db8::6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + } + ] + } + } + }, + { + "identifier": "ISIS", + "name": "ISIS", + "config": { + "identifier": "ISIS", + "name": "ISIS" + }, + "isis": { + "global": { + "config": { + "hello-padding": "DISABLE" + } + }, + "interfaces": { + "interface": [ + { + "interface-id": "port1", + "config": { + "interface-id": "port1", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + }, + { + "interface-id": "port2", + "config": { + "interface-id": "port2", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + } + ] + } + } + } + ] + } + } + ] + }, + "routing-policy": { + "policy-definitions": { + "policy-definition": [ + { + "name": "ALLOW", + "config": { + "name": "ALLOW" + }, + "statements": { + "statement": [ + { + "name": "20", + "config": { + "name": "20" + }, + "actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + } + } + ] + } + } + ] + } + } +} +``` \ No newline at end of file diff --git a/feature/afts/otg_tests/afts_reboot/metadata.textproto b/feature/afts/otg_tests/afts_reboot/metadata.textproto new file mode 100644 index 00000000000..0c9a6fecf44 --- /dev/null +++ b/feature/afts/otg_tests/afts_reboot/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ed774b43-ba3e-4ba9-84b7-e7bdf4d6298d" +plan_id: "AFT-5.1" +description: "AFTs DUT Reboot" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/README.md b/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/README.md new file mode 100644 index 00000000000..2d21caee156 --- /dev/null +++ b/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/README.md @@ -0,0 +1,525 @@ +# AFT-2.1: AFTs Prefix Counters + +## Summary + +IPv4/IPv6 prefix counters + +## Testbed + +* atedut_2.testbed + +## Test Setup + +### Generate DUT and ATE Configuration + +Configure DUT:port1 for IS-IS session with ATE:port1 +* IS-IS must be level 2 only with wide metric. +* IS-IS must be point to point. +* Send 1000 ipv4 and 1000 ipv6 IS-IS prefixes from ATE:port1 to DUT:port1. + +Establish eBGP sessions between ATE:port1 and DUT:port1. +* Configure eBGP over the interface ip. +* Advertise 1000 ipv4,ipv6 prefixes from ATE port1 observe received prefixes at DUT. + +### Procedure + +* Gnmi set with REPLACE option to push the configuration DUT. +* ATE configuration must be pushed. + +### verifications + +* BGP routes advertised from ATE:port1 must have 1 nexthop. +* IS-IS routes advertised from ATE:port1 must have one next hop. +* Use gnmi Subscribe with ON_CHANGE option to /network-instances/network-instance/afts. +* Verify afts prefix entries using the following paths with in a timeout of 30s. + +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix, +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix + + + +## AFT-2.1.1: AFT Prefix Counters ipv4 packets forwarded, ipv4 octets forwarded IS-IS route. + +### Procedure + +From ATE:port2 send 10000 packets to one of the ipv4 prefix advertise by IS-IS. + +### Verifications + +* Before the traffic measure the initial counter value. +* After the traffic measure the final counter value. +* The difference between final and initial value must match with the counter value in ATE then the test is marked as passed. +* Verify afts ipv4 forwarded packets and ipv4 forwarded octets counter entries using the path mentioned in the paths section of this test plan. + +## AFT-2.1.2: AFT Prefix Counters ipv4 packets forwarded, ipv4 octets forwarded BGP route. + +### Procedure + +From ATE:port2 send 10000 packets to one of the ipv4 prefix advertise by BGP. + +### Verifications + +* Before the traffic measure the initial counter value. +* After the traffic measure the final counter value. +* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. +* Verify afts ipv4 forwarded packets and ipv4 forwarded octets counter entries using the path mentioned in the paths section of this test plan. + + +## AFT-2.1.3: AFT Prefix Counters ipv6 packets forwarded, ipv6 octets forwarded IS-IS route. + +### Procedure + +From ATE:port2 send 10000 packets to one of the ipv6 prefix advertise by IS-IS. + +### Verifications + +* Before the traffic measure the initial counter value. +* After the traffic measure the final counter value. +* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. +* Verify afts ipv6 forwarded packets and ipv6 forwarded octets counter entries using the path mentioned in the paths section of this test plan. + +## AFT-2.1.4: AFT Prefix Counters ipv6 packets forwarded, ipv6 octets forwarded BGP route. + +### Procedure + +From ATE:port2 send 10000 packets to one of the ipv6 prefix advertise by BGP. + +### Verifications + +* Before the traffic measure the initial counter value. +* After the traffic measure the final counter value. +* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. +* Verify afts ipv6 forwarded packets and ipv6 forwarded octets counter entries using the path mentioned in the paths section of this test plan. + +## AFT-2.1.5: AFT Prefix Counters withdraw the ipv4 prefix. + +### Procedure + +* From ATE:port1 withdraw some prefixes of BGP and IS-IS. +* Send 10000 packets from ATE:port2 to DUT:port2 for one of the withdrawn ipv4 prefix. +* The traffic must blackhole. + +### Verifications + +* The counters must not send incremental value as the prefix is not present in RIB/FIB. The test fails if the counter shows incremental values. +* Verify afts ipv4 forwarded packet counter entries using the path mentioned in the paths section of this test plan. + +## AFT-2.1.6: AFT Prefix Counters add the ipv4 prefix back. + +### Procedure + +* From ATE:port1 add the prefixes of BGP and IS-IS back. +* Send 10000 packets from ATE:port2 to DUT:port2 for one of the added ipv4 prefix. +* The traffic must flow end to end. + +### Verifications + +* Before the traffic measure the initial counter value. +* After the traffic measure the final counter value. +* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. +* Verify afts counter entries using the path mentioned in the paths section of this test plan. + +## AFT-2.1.7: AFT Prefix Counters withdraw the ipv6 prefix. + +### Procedure + +* From ATE:port1 withdraw some prefixes of BGP and IS-IS. +* Send 10000 packets from ATE:port2 to DUT:port2 for one of the withdrawn ipv6 prefix. +* The traffic must blackhole. + +### Verifications + +* The counters must not send incremental value as the prefix is not present in RIB/FIB. The test fails if the counter shows incremental values. +* Verify afts counter entries using the path mentioned in the paths section of this test plan. + +## AFT-2.1.8: AFT Prefix Counters add the ipv6 prefix back. + +### Procedure + +* From ATE:port1 add the prefixes of BGP and IS-IS back. +* Send 10000 packets from ATE:port2 to DUT:port2 for one of the added ipv6 prefix. +* The traffic must flow end to end. + +### Verifications + +* Before the traffic measure the initial counter value. +* After the traffic measure the final counter value. +* The difference between final and initial value must match with the counter value in ATE, then the test is marked as passed. +* Verify afts counter entries using the path mentioned in the paths section of this test plan. + +## 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: + ## State Paths ## + + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded: + +rpcs: + gnmi: + gNMI.Subscribe: +``` + +## Control Protocol Coverage + +BGP +IS-IS + +## Minimum DUT Platform Requirement + +vRX + +## Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "BGP", + "name": "BGP", + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "bgp": { + "global": { + "config": { + "as": 65501, + "router-id": "192.0.2.1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 2 + } + } + } + } + ] + } + }, + "peer-groups": { + "peer-group": [ + { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P1", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V4-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + }, + { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "config": { + "peer-group-name": "BGP-PEER-GROUP-V6-P2", + "peer-as": 200 + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "apply-policy": { + "config": { + "import-policy": [ + "ALLOW" + ], + "export-policy": [ + "ALLOW" + ] + } + }, + "use-multiple-paths": { "config": { "enabled": true } } + } + ] + } + } + ] + }, + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.0.2.2", + "config": { + "neighbor-address": "192.0.2.2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::2", + "config": { + "neighbor-address": "2001:db8::2", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P1" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "192.0.2.6", + "config": { + "neighbor-address": "192.0.2.6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V4-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::6", + "config": { + "neighbor-address": "2001:db8::6", + "peer-as": 200, + "enabled": true, + "peer-group": "BGP-PEER-GROUP-V6-P2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + } + } + ] + } + } + }, + { + "identifier": "ISIS", + "name": "ISIS", + "config": { + "identifier": "ISIS", + "name": "ISIS" + }, + "isis": { + "global": { + "config": { + "hello-padding": "DISABLE" + } + }, + "interfaces": { + "interface": [ + { + "interface-id": "port1", + "config": { + "interface-id": "port1", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + }, + { + "interface-id": "port2", + "config": { + "interface-id": "port2", + "enabled": true + }, + "levels": { + "level": [ + { + "level-number": 2, + "config": { + "level-number": 2 + } + } + ] + } + } + ] + } + } + } + ] + } + } + ] + }, + "routing-policy": { + "policy-definitions": { + "policy-definition": [ + { + "name": "ALLOW", + "config": { + "name": "ALLOW" + }, + "statements": { + "statement": [ + { + "name": "20", + "config": { + "name": "20" + }, + "actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + } + } + ] + } + } + ] + } + } +} +``` diff --git a/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/metadata.textproto b/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/metadata.textproto new file mode 100644 index 00000000000..ab1824ae0df --- /dev/null +++ b/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "d2bce146-4e21-4a57-b3c1-5a431c45863a" +plan_id: "AFT-2.1" +description: "AFTs Prefix Counters" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/aft/afts_summary/otg_tests/route_summary_counters_test/README.md b/feature/afts/summary/otg_tests/route_summary_counters_test/README.md similarity index 100% rename from feature/aft/afts_summary/otg_tests/route_summary_counters_test/README.md rename to feature/afts/summary/otg_tests/route_summary_counters_test/README.md diff --git a/feature/aft/afts_summary/otg_tests/route_summary_counters_test/metadata.textproto b/feature/afts/summary/otg_tests/route_summary_counters_test/metadata.textproto similarity index 100% rename from feature/aft/afts_summary/otg_tests/route_summary_counters_test/metadata.textproto rename to feature/afts/summary/otg_tests/route_summary_counters_test/metadata.textproto diff --git a/feature/aft/afts_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go b/feature/afts/summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go similarity index 99% rename from feature/aft/afts_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go rename to feature/afts/summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go index e0a77d6d5e1..c3d13f570f3 100644 --- a/feature/aft/afts_summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go +++ b/feature/afts/summary/otg_tests/route_summary_counters_test/route_summary_counters_test.go @@ -104,6 +104,10 @@ func configureOTG(t *testing.T, ts *isissession.TestSession) { t.Log("Starting protocols on ATE...") ts.PushAndStart(t) + _, err := ts.AwaitAdjacency() + if err != nil { + t.Fatalf("No IS-IS adjacency formed: %v", err) + } ts.MustAdjacency(t) } diff --git a/feature/aft/afts_summary/otg_tests/scale_aft_summary/README.md b/feature/afts/summary/otg_tests/scale_aft_summary/README.md similarity index 100% rename from feature/aft/afts_summary/otg_tests/scale_aft_summary/README.md rename to feature/afts/summary/otg_tests/scale_aft_summary/README.md diff --git a/feature/aft/afts_summary/otg_tests/scale_aft_summary/metadata.textproto b/feature/afts/summary/otg_tests/scale_aft_summary/metadata.textproto similarity index 100% rename from feature/aft/afts_summary/otg_tests/scale_aft_summary/metadata.textproto rename to feature/afts/summary/otg_tests/scale_aft_summary/metadata.textproto diff --git a/feature/aft/afts_summary/otg_tests/scale_aft_summary/route_test.go b/feature/afts/summary/otg_tests/scale_aft_summary/route_test.go similarity index 100% rename from feature/aft/afts_summary/otg_tests/scale_aft_summary/route_test.go rename to feature/afts/summary/otg_tests/scale_aft_summary/route_test.go diff --git a/feature/bgp/addpath/feature.textproto b/feature/bgp/addpath/feature.textproto deleted file mode 100644 index 36293f0cccd..00000000000 --- a/feature/bgp/addpath/feature.textproto +++ /dev/null @@ -1,112 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_addpath" - version: 1 -} - -# Global Level - -# AFI/SAFI -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/receive" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/send" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/send-max" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/config/eligible-prefix-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/receive" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/send" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/send-max" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/add-paths/state/eligible-prefix-policy" -} - -# Neighbor Level - -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/receive" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/send" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/send-max" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/config/eligible-prefix-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/receive" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/send" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/send-max" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/add-paths/state/eligible-prefix-policy" -} - -# Peer Groups -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/receive" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/send" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/send-max" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/config/eligible-prefix-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/receive" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/send" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/send-max" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/add-paths/state/eligible-prefix-policy" -} - -feature_profile_dependency { - name: "bgp" - version: 1 -} - -feature_profile_dependency { - name: "bgp_policybase" - version: 1 -} diff --git a/feature/bgp/addpath/otg_tests/addpath_scale/addpath_scale_test.go b/feature/bgp/addpath/otg_tests/addpath_scale/addpath_scale_test.go index d3c1d221774..7ab9cbe0d78 100644 --- a/feature/bgp/addpath/otg_tests/addpath_scale/addpath_scale_test.go +++ b/feature/bgp/addpath/otg_tests/addpath_scale/addpath_scale_test.go @@ -968,6 +968,7 @@ func bgpWithNbr(as uint32, nbrs []*bgpNeighbor, dut *ondatra.DUTDevice, impPolic bgpNbr := bgp.GetOrCreateNeighbor(nbr.neighborip) bgpNbr.GetOrCreateTimers().HoldTime = ygot.Uint16(180) bgpNbr.GetOrCreateTimers().KeepaliveInterval = ygot.Uint16(60) + bgpNbr.GetOrCreateTimers().SetMinimumAdvertisementInterval(10) bgpNbr.PeerAs = ygot.Uint32(nbr.as) bgpNbr.Enabled = ygot.Bool(true) bgpNbr.PeerGroup = ygot.String(nbr.pg) diff --git a/feature/bgp/addpath/otg_tests/route_propagation_test/route_propagation_test.go b/feature/bgp/addpath/otg_tests/route_propagation_test/route_propagation_test.go index 29ce31ff7da..14dbd8ca676 100644 --- a/feature/bgp/addpath/otg_tests/route_propagation_test/route_propagation_test.go +++ b/feature/bgp/addpath/otg_tests/route_propagation_test/route_propagation_test.go @@ -265,7 +265,7 @@ func (d *dutData) AwaitBGPEstablished(t *testing.T, dut *ondatra.DUTDevice) { func getPeerGroup(pgn string, aftype oc.E_BgpTypes_AFI_SAFI_TYPE, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol_Bgp_PeerGroup { bgp := &oc.NetworkInstance_Protocol_Bgp{} pg := bgp.GetOrCreatePeerGroup(pgn) - + pg.GetOrCreateTimers().SetMinimumAdvertisementInterval(10) if deviations.RoutePolicyUnderAFIUnsupported(dut) { //policy under peer group rpl := pg.GetOrCreateApplyPolicy() diff --git a/feature/bgp/admin_distance/otg_tests/admin_distance_test/README.md b/feature/bgp/admin_distance/otg_tests/admin_distance_test/README.md index 464703ecf20..c0094b35f8f 100644 --- a/feature/bgp/admin_distance/otg_tests/admin_distance_test/README.md +++ b/feature/bgp/admin_distance/otg_tests/admin_distance_test/README.md @@ -68,6 +68,39 @@ For each section of configuration below, prepare a gnmi.SetBatch with all the c * Generate traffic from ATE port-3 towards ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` * Validate that the traffic is received on port-1 of the ATE +### RT-1.34.5 [TODO:https://github.com/openconfig/featureprofiles/issues/4233] +#### Validate traffic with modified eBGP Route-Distance of 9 +* Configure eBGP Route-Distance to 9 at DUT and eBGP session on ATE port-2 + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/internal-route-distance +* Validate using gNMI Subscribe with mode 'ONCE' that the correct Route-Distance value of 9 is reported: + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/internal-route-distance +* Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` from OTG port 2 over eBGP +* Configure static route for same ipv4, ipv6 prefix with next-hop towards port-1 and Route-Distance 13 +* Disable isis on port-1 +* Shut-down eBGP session from OTG +* Generate traffic from ATE port-3 towards ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` +* Validate that the traffic is received on port-1 (towards static) of the ATE +* Wait for 10s +* Enable eBGP session back from OTG +* Validate that the traffic is received on port-2 (eBGP peer) of the ATE + +### RT-1.34.6 [TODO:https://github.com/openconfig/featureprofiles/issues/4233] +#### Validate traffic with modified eBGP Route-Distance of 9 and iBGP Route-Distance of 250 +* Remove static route configuration +* Configure eBGP Route-Distance to 9 at DUT and eBGP session on ATE port-2 + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/internal-route-distance +* Configure iBGP Route-Distance to 250 at DUT and iBGP session on ATE port-1 + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/internal-route-distance +* Validate using gNMI Subscribe with mode 'ONCE' that the correct Route-Distance value of 9 and 250 is reported: + * /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/state/internal-route-distance +* Advertise ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` + * From OTG port 1 over iBGP with one ASN on as-path + * From OTG port 2 over eBGP with six ASN on the path +* Generate traffic from ATE port-3 towards ```ipv4-network-1 = 192.168.10.0/24``` and ```ipv6-network-1 = 2024:db8:64:64::/64``` +* Validate that the traffic is received on port-1 (iBGP peer) of the ATE +* Shut down iBGP session on port-1 +* Validate that the traffic is received on port-2 (eBGP peer) of the ATE + ## OpenConfig Path and RPC Coverage The below yaml defines the OC paths intended to be covered by this test. OC diff --git a/feature/bgp/aspath/feature.textproto b/feature/bgp/aspath/feature.textproto deleted file mode 100644 index e82b142eb54..00000000000 --- a/feature/bgp/aspath/feature.textproto +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_aspath" - version: 1 -} - -# Neighbor Level - -#allow-own-as with options -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/config/allow-own-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/config/replace-peer-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/config/disable-peer-as-filter" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/state/allow-own-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/state/replace-peer-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/as-path-options/state/disable-peer-as-filter" -} - -# Peer Groups -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/config/allow-own-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/config/replace-peer-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/config/disable-peer-as-filter" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/state/allow-own-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/state/replace-peer-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/as-path-options/state/disable-peer-as-filter" -} - -feature_profile_dependency { - name: "bgp" - version: 1 -} diff --git a/feature/bgp/bestpath/feature.textproto b/feature/bgp/bestpath/feature.textproto deleted file mode 100644 index 48d03763a8f..00000000000 --- a/feature/bgp/bestpath/feature.textproto +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_bestpath" - version: 1 -} - -# Global Level - -#always-compare-med -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/config/always-compare-med" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/state/always-compare-med" -} - -#ignore-as-path-length -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/config/ignore-as-path-length" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/state/ignore-as-path-length" -} - -#compare-rid -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/config/external-compare-router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/state/external-compare-router-id" -} - -#advertise-inactive-routes -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/config/advertise-inactive-routes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/route-selection-options/state/advertise-inactive-routes" -} - - -feature_profile_dependency { - name: "bgp" - version: 1 -} diff --git a/feature/bgp/bmp/otg_tests/bmp_base_session_test/README.md b/feature/bgp/bmp/otg_tests/bmp_base_session_test/README.md new file mode 100644 index 00000000000..9e584b42907 --- /dev/null +++ b/feature/bgp/bmp/otg_tests/bmp_base_session_test/README.md @@ -0,0 +1,159 @@ +# BMP-1.1: BMP Session Establishment and Telemetry Test + +## Summary + +Validate BGP Monitoring Protocol (BMP) session establishment and telemetry functionality with specific configuration parameters. +This BMP test is for post policy routes. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Configuration + +1) Create the topology below: + + +```mermaid +graph LR; +A[ATE:Port1] --EBGP--> B[Port1:DUT:Port2]; +B --EBGP--> C[Port2:ATE]; +``` + + +2) Establish Topology: Set up the physical connections as described in the topology diagram. + +* Connect ATE Port 1 to DUT Port 1. +* Connect ATE Port 2 to DUT Port 2 (simulating the BMP monitoring station). +* Connected interface IPv4/IPv6 addresses + +3) Interface Configuration: Configure IPv4 and IPV6 addresses on all involved interfaces on both the ATE and the DUT. + +4) Establish eBGP Session: Configure and establish an external BGP (eBGP) peering session between the IP addresses of ATE Port 1 and DUT Port 1. Configure AS 64520 on the DUT and ATE port2 i.e. BMP station, configure AS 64530 on the ATE port1. +5) Establish eBGP session between ATE port-1 (AS 64530) and DUT port-1 (AS 64520) +6) Configure BMP on the DUT with the following parameters: + + • statistics-timeout: 60 seconds + • connection-mode: active + • local-address: 172.16.1.1 + • station-address: 10.23.15.58 + • station-port: 7039 + • route-monitoring: post-policy + • exclude-noneligible: true + +7) Advertise 10000 * IPV4 routes from the IP address range of 192.168.0.0 and 10.200.0.0 +8) Should be RFC7854 compliant + + +### Tests + +### BMP-1.1.1: Verify BMP session establishment + +1) Configure BMP station on ATE port-2 with address 10.23.15.58 and port 7039 +2) Verify that the DUT initiates a BMP session to the station (connection-mode: active), following this path: +3) Confirm the connection is established using the configured local-address (172.16.1.1), following this path: +4) Validate that the DUT connects to the correct station-address (10.23.15.58) and port (7039), following those paths: +5) Check the session-state telemetry path to confirm the session is established + + +### BMP-1.1.2: Verify statistics reporting + +1) Verify that the DUT sends statistics reports at the configured interval (60 seconds), using path: +2) Confirm that multiple consecutive reports are sent at the expected intervals, using path: + +### BMP-1.1.3: Verify route monitoring with post-policy and exclude-noneligible + +1) Configure an import policy on the DUT to reject prefixes matching 172.16.0.0/16 and 2001:DB8::/32 +2) Have ATE port-1 advertise prefixes 192.0.2.0/24 and 172.16.0.0/16 for IPv4 and 2001:DB8:1::/48 and 2001:DB8::/32 for IPv6 to the DUT +3) Verify that the BMP station receives route monitoring messages for 192.0.2.0/24 2001:DB8:1::/48, using those paths: +4) Verify that the BMP station does not receive route monitoring messages for 172.16.0.0/16 and 2001:DB8::/32 (excluded by policy) + + +## OC paths in json format: + +### TODO: Add values +```json +{ + "network-instances": { + "network-instance": { + "protocols": { + "protocol": { + "bgp": { + "global": { + "bmp": { + "config": { + "enabled": , + "connection-mode": , + "local-address": , + "statistics-timeout": + }, + "stations": { + "station": { + "config": { + "name": , + "address": , + "port": , + "policy-type": , + "exclude-non-eligible": + } + } + } + } + } + } + } + } + } + } +} + +``` + + + +## OpenConfig Path and RPC Coverage + +```yaml + ## Config paths + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/config/connection-mode: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/config/local-address: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/config/statistics-timeout: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/config/name: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/config/address: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/config/port: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/config/policy-type: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/config/exclude-non-eligible: + + ## state paths + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/enabled: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/connection-mode: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/local-address: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/statistics-timeout: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/idle-time: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/probe-count: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/state/probe-interval: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/address: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/port: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/connection-status: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/uptime: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/flap-count: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/policy-type: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/exclude-non-eligible: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/message-counters/total: + /network-instances/network-instance/protocols/protocol/bgp/global/bmp/stations/station/state/message-counters/statistics: + + rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: +``` + +## Required DUT platform + * vRX - virtual router device + + diff --git a/feature/bgp/bmp/otg_tests/bmp_base_session_test/metadata.textproto b/feature/bgp/bmp/otg_tests/bmp_base_session_test/metadata.textproto new file mode 100644 index 00000000000..3ffd70bc00b --- /dev/null +++ b/feature/bgp/bmp/otg_tests/bmp_base_session_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ae321451-e937-4f99-885d-bec2bf616702" +plan_id: "BMP-1.1" +description: "BMP Session Establishment and Telemetry Test" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/bgp/dynamicneighbors/feature.textproto b/feature/bgp/dynamicneighbors/feature.textproto deleted file mode 100644 index 10b2877f662..00000000000 --- a/feature/bgp/dynamicneighbors/feature.textproto +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_dynamicneighbors" - version: 1 -} - -# Global Level - -# dynamic-neighbor-prefixes -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/dynamic-neighbor-prefixes/dynamic-neighbor-prefix/config/prefix" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/dynamic-neighbor-prefixes/dynamic-neighbor-prefix/config/peer-group" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/dynamic-neighbor-prefixes/dynamic-neighbor-prefix/state/prefix" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/dynamic-neighbor-prefixes/dynamic-neighbor-prefix/state/peer-group" -} - -feature_profile_dependency { - name: "bgp" - version: 1 -} - diff --git a/feature/bgp/feature.textproto b/feature/bgp/feature.textproto deleted file mode 100644 index fcc9e02b447..00000000000 --- a/feature/bgp/feature.textproto +++ /dev/null @@ -1,473 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp" - version: 1 -} - -# BGP Instance -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/config/as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/state/as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/state/total-prefixes" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/afi-safi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/state/afi-safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/state/enabled" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/state/extended-next-hop-encoding" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/config/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/state/router-id" -} - -# Neighbor Level - -# Static Peer -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/enabled" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/neighbor-address" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/neighbor-port" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/neighbor-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/neighbor-port" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/established-transitions" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/active" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/queues/input" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/queues/output" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/supported-capabilities" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/negotiated-hold-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/local-port" -} - - -# messages - -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/sent/UPDATE" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/UPDATE" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/sent/NOTIFICATION" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/NOTIFICATION" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/last-notification-error-code" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/last-notification-error-subcode" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/received/last-notification-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/sent/last-notification-error-code" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/messages/sent/last-notification-error-subcode" -} - -# AFI/SAFI - IPv4 unicast, IPv6 unicast - -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/config/afi-safi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/afi-safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/enabled" -} - -# Neighbor Peer Group -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-group" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-group" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/total-prefixes" -} - -# state log -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/logging-options/config/log-neighbor-state-changes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/logging-options/state/log-neighbor-state-changes" -} - -# Password Encryption: MD5 -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/auth-password" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/auth-password" -} - -# Description -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/description" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/description" -} - -# Passive Peer -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/passive-mode" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/passive-mode" -} - -# TCP MSS -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/tcp-mss" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/tcp-mss" -} - -# PMTUD -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/mtu-discovery" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/mtu-discovery" -} - -# local-address -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/local-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/local-address" -} - -# local-as -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/local-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/local-as" -} - -# peer-as -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as" -} - -# peer-type -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-type" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-type" -} - -# remove-private-as -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/remove-private-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/remove-private-as" -} - -# send-community -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/send-community" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/send-community" -} - -# max-prefix: IPv4 unicast, IPv6 unicast -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/max-prefixes" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/prevent-teardown" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/warning-threshold-pct" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/state/max-prefixes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/state/prevent-teardown" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/state/warning-threshold-pct" -} - -#Basic BGP Timers -## MRAI -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/minimum-advertisement-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/minimum-advertisement-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/restart-time" -} - -##Hold Time -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/hold-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/hold-time" -} - -##Keepalive -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/keepalive-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/keepalive-interval" -} - -##Connect Retry -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/connect-retry" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/connect-retry" -} - - -# Peer Groups -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/peer-group-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/peer-group-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/peer-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/peer-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/local-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/local-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/description" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/description" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/peer-type" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/peer-type" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/auth-password" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/auth-password" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/remove-private-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/remove-private-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/send-community" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/send-community" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/config/passive-mode" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/state/passive-mode" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/config/afi-safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/state/afi-safi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/state/enabled" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/minimum-advertisement-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/minimum-advertisement-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/hold-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/hold-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/keepalive-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/keepalive-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/config/mtu-discovery" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/state/mtu-discovery" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/config/local-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/transport/state/local-address" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/max-prefixes" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/prevent-teardown" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/warning-threshold-pct" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/state/max-prefixes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/state/prevent-teardown" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/state/warning-threshold-pct" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/minimum-advertisement-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/minimum-advertisement-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/hold-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/hold-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/keepalive-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/keepalive-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/connect-retry" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/connect-retry" -} - -# Neighbor state -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/last-established" -} - -# Transport -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/remote-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/remote-port" -} - -feature_profile_dependency { - name: "networkinstance" - version: 1 -} diff --git a/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/README.md b/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/README.md index d84c42a4972..67aa3a5e3bb 100644 --- a/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/README.md +++ b/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/README.md @@ -24,7 +24,7 @@ B -- IBGP --> C[Port2:ATE]; * Configure EBGP peering between ATE:Port1 and DUT:Port1 * Configure IBGP peering between ATE:Port2 and DUT:Port2 * Ensure that the EBGP and IBGP peering are setup for IPv4-Unicast and IPv6-unicast AFI-SAFIs. Total 2xpeer-groups (1 per protocol) with 1 BGP session each. -* Enable `Graceful-Restart` capability at the `Peer-Group` level. +* Enable `Graceful-Restart` capability at the `Peer-Group` level once and also repeat all steps below by enabling `Graceful-Restart` at `neighbor` level * Ensure that the `restart-time` and the `stale-routes-time` are configured at the `Global` level. The `stale-routes-time` should be set at a value less than the BGP Holddown timer. * Configure allow route-policy under BGP peer-group address-family * Validate received capabilities at DUT and ATE reflect support for graceful @@ -102,6 +102,59 @@ The origial RFC4724 had no coverage for Graceful restart process post send/recei * Start traffic from ATE Port1 towards ATE Port2. Confirm there is zero packet loss. Stop traffic. * Revert ATE configurtion blocking TCP connection to/from DUT over TCP-Port:179 so the EBGP peering between ATE:Port1 <> DUT:port1 is reestablished. Restart traffic and confirm that there is zero packet loss. * Restart the above procedure for the IBGP peering between DUT port-2 and ATE port-2 + +## Canonical OpenConfig for keepalive-interval and hold-time at neighbour and peer-group level for BGP + +```json +{ + "network-instances": { + "network-instance": { + "DEFAULT": { + "protocols": { + "protocol": { + "BGP": { + "BGP": { + "bgp": { + "peer-groups": { + "peer-group": { + "BGP-PEER-GROUP-V4": { + "afi-safis": { + "afi-safi": { + "IPV4_UNICAST": { + "graceful-restart": { + "state": { + "enabled": true + } + } + } + } + } + }, + "BGP-PEER-GROUP-V6": { + "afi-safis": { + "afi-safi": { + "IPV6_UNICAST": { + "graceful-restart": { + "state": { + "enabled": true + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} +``` ## Config Parameter Coverage For prefixes: @@ -113,6 +166,7 @@ For prefixes: Parameters: * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/enabled +* /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/graceful-restart/config/enabled * /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/helper-only * /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/restart-time * /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/stale-routes-time @@ -150,4 +204,4 @@ rpcs: gNMI.Subscribe: gnoi: system.System.KillProcess: -``` \ No newline at end of file +``` diff --git a/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/bgp_graceful_restart_test.go b/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/bgp_graceful_restart_test.go index 8eacae295cf..6d1b6d266d8 100644 --- a/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/bgp_graceful_restart_test.go +++ b/feature/bgp/gracefulrestart/ate_tests/bgp_graceful_restart_test/bgp_graceful_restart_test.go @@ -15,6 +15,7 @@ package bgp_graceful_restart_test import ( + "fmt" "testing" "time" @@ -169,7 +170,7 @@ func buildNbrList(asN uint32) []*bgpNeighbor { return []*bgpNeighbor{nbr1v4, nbr2v4, nbr1v6, nbr2v6} } -func bgpWithNbr(as uint32, nbrs []*bgpNeighbor, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { +func bgpWithNbr(as uint32, nbrs []*bgpNeighbor, dut *ondatra.DUTDevice, test string) *oc.NetworkInstance_Protocol { d := &oc.Root{} ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") @@ -180,18 +181,27 @@ func bgpWithNbr(as uint32, nbrs []*bgpNeighbor, dut *ondatra.DUTDevice) *oc.Netw g.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) g.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) g.RouterId = ygot.String(dutDst.IPv4) - bgpgr := g.GetOrCreateGracefulRestart() - bgpgr.Enabled = ygot.Bool(true) - bgpgr.RestartTime = ygot.Uint16(grRestartTime) - bgpgr.StaleRoutesTime = ygot.Uint16(grStaleRouteTime) - pg := bgp.GetOrCreatePeerGroup(peerv4GrpName) pg.PeerAs = ygot.Uint32(ateAS) pg.PeerGroupName = ygot.String(peerv4GrpName) - pgv6 := bgp.GetOrCreatePeerGroup(peerv6GrpName) pgv6.PeerAs = ygot.Uint32(ateAS) pgv6.PeerGroupName = ygot.String(peerv6GrpName) + bgpgr := g.GetOrCreateGracefulRestart() + bgpgr.RestartTime = ygot.Uint16(grRestartTime) + bgpgr.StaleRoutesTime = ygot.Uint16(grStaleRouteTime) + if test == "GRUnderNeighbor" { + bgpgr.Enabled = ygot.Bool(true) + bgpgr.HelperOnly = ygot.Bool(false) + } else { + + pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).SetEnabled(true) + pg1af4Gr := pg.GetAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateGracefulRestart() + pg1af4Gr.SetEnabled(true) + pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).SetEnabled(true) + pg1af6Gr := pgv6.GetAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GetOrCreateGracefulRestart() + pg1af6Gr.SetEnabled(true) + } if deviations.RoutePolicyUnderAFIUnsupported(dut) { rpl := pg.GetOrCreateApplyPolicy() @@ -245,7 +255,7 @@ func bgpWithNbr(as uint32, nbrs []*bgpNeighbor, dut *ondatra.DUTDevice) *oc.Netw return niProto } -func checkBgpStatus(t *testing.T, dut *ondatra.DUTDevice) { +func checkBgpStatus(t *testing.T, dut *ondatra.DUTDevice, test string, nbrs []*bgpNeighbor) { t.Log("Verifying BGP state") statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() nbrPath := statePath.Neighbor(ateSrc.IPv4) @@ -272,15 +282,24 @@ func checkBgpStatus(t *testing.T, dut *ondatra.DUTDevice) { fptest.LogQuery(t, "BGPv6 reported state", nbrPathv6.State(), gnmi.Get(t, dut, nbrPathv6.State())) t.Fatal("No BGPv6 neighbor formed...") } - - isGrEnabled := gnmi.Get(t, dut, statePath.Global().GracefulRestart().Enabled().State()) - t.Logf("isGrEnabled %v", isGrEnabled) - if isGrEnabled { - t.Logf("Graceful restart on neighbor %v enabled as Expected", ateDst.IPv4) + if test == "GRUnderNeighbor" { + isGrEnabled := gnmi.Get(t, dut, statePath.Global().GracefulRestart().Enabled().State()) + t.Logf("isGrEnabled %v", isGrEnabled) + if isGrEnabled { + t.Logf("Graceful restart on neighbor %v enabled as Expected", ateDst.IPv4) + } else { + t.Errorf("Expected Graceful restart status on neighbor: got %v, want Enabled", isGrEnabled) + } } else { - t.Errorf("Expected Graceful restart status on neighbor: got %v, want Enabled", isGrEnabled) + isGrEnabledv4 := gnmi.Get(t, dut, statePath.PeerGroup(peerv4GrpName).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GracefulRestart().Enabled().State()) + isGrEnabledv6 := gnmi.Get(t, dut, statePath.PeerGroup(peerv6GrpName).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).GracefulRestart().Enabled().State()) + t.Logf("isGrEnabledv4 %v and isGrEnabledv6 %v", isGrEnabledv4, isGrEnabledv6) + if isGrEnabledv4 { + t.Logf("Graceful restart on neighbor %v enabled as Expected", ateDst.IPv4) + } else { + t.Errorf("Expected Graceful restart status on neighbor: got GrEnabledv4 %v and GrEnabledv6 %v, want Enabled for both", isGrEnabledv4, isGrEnabledv6) + } } - grTimerVal := gnmi.Get(t, dut, statePath.Global().GracefulRestart().RestartTime().State()) t.Logf("grTimerVal %v", grTimerVal) if grTimerVal == uint16(grRestartTime) { @@ -288,7 +307,6 @@ func checkBgpStatus(t *testing.T, dut *ondatra.DUTDevice) { } else { t.Errorf("Expected Graceful restart timer: got %v, want %v", grTimerVal, grRestartTime) } - t.Log("Waiting for BGP v4 prefix to be installed") got, found := gnmi.Watch(t, dut, nbrPath.AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes().Installed().State(), 180*time.Second, func(val *ygnmi.Value[uint32]) bool { prefixCount, ok := val.Val() @@ -463,120 +481,138 @@ func configACLInterface(t *testing.T, iFace *oc.Acl_Interface, ifName string) *a return aclConf } +func bgpClearConfig(t *testing.T, dut *ondatra.DUTDevice) { + resetBatch := &gnmi.SetBatch{} + gnmi.BatchDelete(resetBatch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config()) + + if deviations.NetworkInstanceTableDeletionRequired(dut) { + tablePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableAny() + for _, table := range gnmi.LookupAll[*oc.NetworkInstance_Table](t, dut, tablePath.Config()) { + if val, ok := table.Val(); ok { + if val.GetProtocol() == oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP { + gnmi.BatchDelete(resetBatch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Table(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, val.GetAddressFamily()).Config()) + } + } + } + } + resetBatch.Set(t, dut) +} + func TestTrafficWithGracefulRestartSpeaker(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") - - // Configure interface on the DUT - t.Run("configureDut", func(t *testing.T) { - t.Log("Start DUT interface Config") - configureDUT(t, dut) - configureRoutePolicy(t, dut, "ALLOW", oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) - }) - - // Configure BGP+Neighbors on the DUT - t.Run("configureBGP", func(t *testing.T) { - t.Log("Configure BGP with Graceful Restart option under Global Bgp") - dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - nbrList := buildNbrList(ateAS) - dutConf := bgpWithNbr(dutAS, nbrList, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) - }) - // ATE Configuration. - var allFlows []*ondatra.Flow - t.Run("configureATE", func(t *testing.T) { - t.Log("Start ATE Config") - allFlows = configureATE(t, ate) - }) - // Verify Port Status - t.Run("verifyDUTPorts", func(t *testing.T) { - t.Log("Verifying port status") - verifyPortsUp(t, dut.Device) - }) - t.Run("VerifyBGPParameters", func(t *testing.T) { - t.Log("Check BGP parameters") - checkBgpStatus(t, dut) - }) - // Starting ATE Traffic - t.Run("VerifyTrafficPassBeforeAcLBlock", func(t *testing.T) { - t.Log("Send Traffic with GR timer enabled. Traffic should pass") - sendTraffic(t, ate, allFlows, trafficDuration) - verifyNoPacketLoss(t, ate, allFlows) - }) - // Configure an ACL to block BGP - d := &oc.Root{} - ifName := dut.Port(t, "port2").Name() - iFace := d.GetOrCreateAcl().GetOrCreateInterface(ifName) - t.Run("VerifyTrafficPasswithGRTimerWithAclApplied", func(t *testing.T) { - t.Log("Configure Acl to block BGP on port 179") - const stopDuration = 45 * time.Second - t.Log("Starting traffic") - ate.Traffic().Start(t, allFlows...) - startTime := time.Now() - t.Log("Trigger Graceful Restart on ATE") - ate.Actions().NewBGPGracefulRestart().WithRestartTime(grRestartTime * time.Second).WithPeers(bgpPeer).Send(t) - gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configACL(d, aclName)) - aclConf := configACLInterface(t, iFace, ifName) - gnmi.Replace(t, dut, aclConf.Config(), iFace) - replaceDuration := time.Since(startTime) - time.Sleep(grTimer - stopDuration - replaceDuration) - t.Log("Send Traffic while GR timer counting down. Traffic should pass as BGP GR is enabled!") - ate.Traffic().Stop(t) - t.Log("Traffic stopped") - verifyNoPacketLoss(t, ate, allFlows) - }) - - statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() - nbrPath := statePath.Neighbor(ateDst.IPv4) - t.Run("VerifyBGPNOTEstablished", func(t *testing.T) { - t.Log("Waiting for BGP neighbor to Not be in Established state after applying ACL DENY policy..") - _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { - currState, ok := val.Val() - return ok && currState != oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) - t.Errorf("BGP session did not go Down as expected") - } - }) - - t.Log("Wait till LLGR/Stale timer expires to delete long live routes.....") - time.Sleep(time.Second * grRestartTime) - time.Sleep(time.Second * grStaleRouteTime) - - t.Run("VerifyTrafficFailureAfterGRexpired", func(t *testing.T) { - t.Log("Send Traffic Again after GR timer has expired. This traffic should fail!") - sendTraffic(t, ate, allFlows, trafficDuration) - confirmPacketLoss(t, ate, allFlows) - }) - - t.Run("RemoveAclInterface", func(t *testing.T) { - t.Log("Removing Acl on the interface to restore BGP GR. Traffic should now pass!") - gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configAdmitAllACL(d, aclName)) - aclPath := configACLInterface(t, iFace, ifName) - gnmi.Replace(t, dut, aclPath.Config(), iFace) - }) - - t.Run("VerifyBGPEstablished", func(t *testing.T) { - t.Logf("Waiting for BGP neighbor to establish...") - _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { - currState, ok := val.Val() - return ok && currState == oc.Bgp_Neighbor_SessionState_ESTABLISHED - }).Await(t) - if !ok { - fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) - t.Errorf("BGP session not Established as expected") - } - }) - - t.Run("VerifyTrafficPassBGPRestored", func(t *testing.T) { - status := gnmi.Get(t, dut, nbrPath.SessionState().State()) - if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; status != want { - t.Errorf("Get(BGP peer %s status): got %d, want %d", ateDst.IPv4, status, want) - } - sendTraffic(t, ate, allFlows, trafficDuration) - verifyNoPacketLoss(t, ate, allFlows) - }) - + testCaseBgpGracefulRestart := []string{"GRUnderNeighbor", "GRUnderPeerGroup"} + for _, test := range testCaseBgpGracefulRestart { + t.Run(fmt.Sprintf("Test BGP Graceful Restart with Speaker under %v", test), func(t *testing.T) { + // Configure interface on the DUT + t.Logf("configureDut") + t.Log("Start DUT interface Config") + configureDUT(t, dut) + configureRoutePolicy(t, dut, "ALLOW", oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) + + // Configure BGP+Neighbors on the DUT + t.Logf("configureBGP") + t.Log("Configure BGP with Graceful Restart option under Global Bgp") + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + nbrList := buildNbrList(ateAS) + dutConf := bgpWithNbr(dutAS, nbrList, dut, test) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + fptest.LogQuery(t, "DUT BGP Config", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + // ATE Configuration. + var allFlows []*ondatra.Flow + t.Logf("configureATE") + t.Log("Start ATE Config") + allFlows = configureATE(t, ate) + + // Verify Port Status + t.Logf("verifyDUTPorts") + t.Log("Verifying port status") + verifyPortsUp(t, dut.Device) + + t.Logf("VerifyBGPParameters") + t.Log("Check BGP parameters") + checkBgpStatus(t, dut, test, nbrList) + + // Starting ATE Traffic + t.Run("VerifyTrafficPassBeforeAcLBlock", func(t *testing.T) { + t.Log("Send Traffic with GR timer enabled. Traffic should pass") + sendTraffic(t, ate, allFlows, trafficDuration) + verifyNoPacketLoss(t, ate, allFlows) + }) + + // Configure an ACL to block BGP + d := &oc.Root{} + ifName := dut.Port(t, "port2").Name() + iFace := d.GetOrCreateAcl().GetOrCreateInterface(ifName) + t.Run("VerifyTrafficPasswithGRTimerWithAclApplied", func(t *testing.T) { + t.Log("Configure Acl to block BGP on port 179") + const stopDuration = 45 * time.Second + t.Log("Starting traffic") + ate.Traffic().Start(t, allFlows...) + startTime := time.Now() + t.Log("Trigger Graceful Restart on ATE") + ate.Actions().NewBGPGracefulRestart().WithRestartTime(grRestartTime * time.Second).WithPeers(bgpPeer).Send(t) + gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configACL(d, aclName)) + aclConf := configACLInterface(t, iFace, ifName) + gnmi.Replace(t, dut, aclConf.Config(), iFace) + replaceDuration := time.Since(startTime) + time.Sleep(grTimer - stopDuration - replaceDuration) + t.Log("Send Traffic while GR timer counting down. Traffic should pass as BGP GR is enabled!") + ate.Traffic().Stop(t) + t.Log("Traffic stopped") + verifyNoPacketLoss(t, ate, allFlows) + }) + + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + nbrPath := statePath.Neighbor(ateDst.IPv4) + t.Logf("VerifyBGPNOTEstablished") + t.Log("Waiting for BGP neighbor to Not be in Established state after applying ACL DENY policy..") + _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + currState, ok := val.Val() + return ok && currState != oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Errorf("BGP session did not go Down as expected") + } + + t.Log("Wait till LLGR/Stale timer expires to delete long live routes.....") + time.Sleep(time.Second * grRestartTime) + time.Sleep(time.Second * grStaleRouteTime) + + t.Run("VerifyTrafficFailureAfterGRexpired", func(t *testing.T) { + t.Log("Send Traffic Again after GR timer has expired. This traffic should fail!") + sendTraffic(t, ate, allFlows, trafficDuration) + confirmPacketLoss(t, ate, allFlows) + }) + + t.Logf("RemoveAclInterface") + t.Log("Removing Acl on the interface to restore BGP GR. Traffic should now pass!") + gnmi.Replace(t, dut, gnmi.OC().Acl().AclSet(aclName, oc.Acl_ACL_TYPE_ACL_IPV4).Config(), configAdmitAllACL(d, aclName)) + aclPath := configACLInterface(t, iFace, ifName) + gnmi.Replace(t, dut, aclPath.Config(), iFace) + + t.Logf("VerifyBGPEstablished") + t.Logf("Waiting for BGP neighbor to establish...") + _, ok = gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + currState, ok := val.Val() + return ok && currState == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Errorf("BGP session not Established as expected") + } + + t.Run("VerifyTrafficPassBGPRestored", func(t *testing.T) { + status := gnmi.Get(t, dut, nbrPath.SessionState().State()) + if want := oc.Bgp_Neighbor_SessionState_ESTABLISHED; status != want { + t.Errorf("Get(BGP peer %s status): got %d, want %d", ateDst.IPv4, status, want) + } + sendTraffic(t, ate, allFlows, trafficDuration) + verifyNoPacketLoss(t, ate, allFlows) + }) + }) + bgpClearConfig(t, dut) + } } diff --git a/feature/bgp/gracefulrestart/feature.textproto b/feature/bgp/gracefulrestart/feature.textproto deleted file mode 100644 index 4e28255f0f0..00000000000 --- a/feature/bgp/gracefulrestart/feature.textproto +++ /dev/null @@ -1,103 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_gracefulrestart" - version: 1 -} - -# Peer Group graceful restart leafs. -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/enabled" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/restart-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/stale-routes-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/helper-only" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/state/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/state/restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/state/stale-routes-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/state/helper-only" -} - -# Per neighbor graceful restart leafs. -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/config/enabled" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/config/restart-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/config/stale-routes-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/config/helper-only" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/stale-routes-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/helper-only" -} - -# Per neighbor telemetry paths -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/graceful-restart/state/advertised" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/peer-restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/graceful-restart/state/received" -} - -# Global telemetry paths -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/restart-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/stale-routes-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/state/restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/state/stale-routes-time" -} - -feature_profile_dependency { - name: "bgp" - version: 1 -} diff --git a/feature/bgp/gracefulrestart/otg_tests/exrr/README.md b/feature/bgp/gracefulrestart/otg_tests/exrr/README.md new file mode 100644 index 00000000000..e30479d9143 --- /dev/null +++ b/feature/bgp/gracefulrestart/otg_tests/exrr/README.md @@ -0,0 +1,588 @@ +# RT-1.35: BGP Graceful Restart Extended route retention (ExRR) + +## Summary + +This is an extension to the RFC8538 tests already conducted under "RT-1.4: BGP +Graceful Restart". However, ExRR is for projects that need to extend the validity +of a route beyond the expiration of the stale routes timer for the BGP GR +process. Following are the scenarios when ExRR can be considered by a project. + +1. Upon expiration of BGP hold-timer (Hold timer expiry on the Speaker side or + when a notification for hold timer expiry is received from the helper) +2. Upon the BGP session failing to re-establish within the GR restart timer as + a helper. +3. Upon multiple failures on the Speaker side resulting in GR restart timer or + the stale path timer not to expire on the helper side. +4. Upon expiration of the stale path timer Under the aforementioned conditions, + the routes received from the neighbor under failure must be held for a + configurable duration and processed through an additional configurable + routing policy while being held in a “stale” state. + +Since the route retention is purely local action of the receiving speaker, this +action should not require any additional capabilities advertisements beyond +capability 64 (Graceful Restart), and should not be confused with or require +capability 71 (Long-Lived Graceful Restart) from the sending speaker. + +**How is this different from LLGR as tested in RT-1.14?** + +As per the [IETF Draft on LLGR](https://tools.ietf.org/html/draft-ietf-idr-long-lived-gr), +we have the following that is different from EER. +* Section 4.2 / 4.3 of the draft: mandates what communities are in use and + what their specific behavior should be. For example: "The "LLGR_STALE" + community must be advertised by the GR helper and also MUST NOT be removed + by other receiving peers." and anyone that receives that route MUST treat + the route as least-preferred. This isnt the case for ERR. There arent any + communities attached to Stale routes thereby mandating their depreference. +* Section 4.7: Different conditions for partial deployment of LLGR is a no-op + for ExRR as it builds on the concepts of RFC8538 and hence there arent any + special communities expected to be sent or received for the stale routes. + +**More about the ExRR policy** + +* This policy can be attached at the Global, Peer-group or Neighbor levels. * + The routes passed through the retention-policy should be the post-policy + adj-rib-in of the neighbor. Any other import policy applied to the routes + must not be overridden by this policy, it should be additive. +* Default action if no ExRR policy is specified should be to follow RFC8538 + behavior. +* Please Note: In the case of an ExRR policy, when the action of a given MATCH + criteria is REJECT, the matching prefixes will be treated similar to RFC8538 + expectations. Therefore such prefixes wouldnt experience extended Retention. + Similarly, when the policy match condition translates to an ACCEPT action, + the prefixes are considered for ExRR operation and the configured Retention + time becomes applicable. The Prefix also gets other attributes as configured + part of ACTION +* Yang definitions for ExRR is proposed in + [pull/1319](https://github.com/openconfig/public/pull/1319). Following is a + representation of how the entire config/state used in this test will look + like. + +### Config OC path for ERR + +``` +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/config/neighbor-address = "192.168.1.1" +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/config/hold-time = 30 +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/config/enabled = true +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/config/restart-time = 300 +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/extended-route-retention/config/enabled = True +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/extended-route-retention/config/retention-time = 15552000 +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/extended-route-retention/config/retention-policy = "STALE-ROUTE-POLICY" +``` + +### State OC path for ERR. + +``` +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/state +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/state/hold-time +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/state/enabled +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/state/restart-time +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/extended-route-retention/state/enabled +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/extended-route-retention/state/retention-time +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor[neighbor-address='192.168.1.1']/graceful-restart/extended-route-retention/state/retention-policy +``` + +## Testbed type + +[atedut_2.testbed](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Topology + +Create the following connections: + +```mermaid + graph LR + A[ATE:Port1] <-- IBGP(ASN100) --> B[Port1:DUT:Port2] + B <-- EBGP(ASN200) --> C[Port2:ATE] +``` + +## Test environment setup + +* ATE:Port1 runs IBGP and must advertise the following IPv4 and IPv6 prefixes + with the corresponding community attributes + * IPv4Prefix1 and IPv6Prefix1 with community NO-ERR + * IPv4Prefix2 and IPv6Prefix2 with community ERR-NO-DEPREF + * IPv4Prefix3 and IPv6Prefix3 with community TEST-IBGP +* ATE:Port2 runs EBGP and must advertise the following IPv4 and IPv6 prefixes + with the corresponding community attributes + * IPv4Prefix4 and IPv6Prefix4 with community NO-ERR + * IPv4Prefix5 and IPv6Prefix5 with community ERR-NO-DEPREF + * IPv4Prefix6 and IPv6Prefix6 with community TEST-EBGP +* DUT has the following configuration on its IBGP and EBGP peering + + * Extended route retention (ERR) enabled. + * ExRR configuration has the retention time of 300 secs configured + * ExRR has a retention-policy `STALE-ROUTE-POLICY` attached. + * "STALE-ROUTE-POLICY" has policy-statements to identify routes tagged + with community `NO-ERR` and have an action of "REJECT" so such routes + aren't considered for ExRR but only GR (RFC8538) + * identify routes tagged with community `ERR-NO-DEPREF` and have an action + of "ACCEPT" so such routes are considered for ERR. Also ADD community + `STALE` to the existing community list attached as part of the regular + adj-rib-in post policy for the route. + * Catch-all rule to identify and accept all other prefixes, attach a + local-preference of "0" and ADD community `STALE` to the existing + community list. + * DUT has import-policy importibgp and export-policy exportibgp towards + the IBGP neighbor applied in the import and export directions + respectively. + * DUT has import-policy importebgp and export-policy exportebgp towards + the EBGP neighbor applied in the import and export directions + respectively. + * "importibgp" policy matches routes with community `testibgp` and updates + the local-preference to 200. The policy has a catch-all statement that + matches all other routes and accepts them. + * "exportibgp policy matches routes with MED 50 and sets community + "NEW-IBGP" + * "importebgp" policy matches community "TEST-EBGP" and sets MED 50 + * "exportebgp" policy matches community "TESTIBGP" and sets + AS-PATH-PREPEND of the local ASN (100) twice and also attaches a new + community "NEW-EBGP" + * DUT has the following added config + * hold-time 30 + * graceful-restart restart-time = 220 secs + * graceful-restart stale-routes-timer = 250 secs + +* Test Flows used for verification + + * IPv4Prefix1 <-> IPv4Prefix4, IPv6Prefix1 <-> IPv6Prefix4 + * IPv4Prefix2 <-> IPv4Prefix5, IPv6Prefix2 <-> IPv6Prefix5 + * IPv4Prefix3 <-> IPv4Prefix6, IPv6Prefix3 <-> IPv6Prefix6 + + +>> Tabular representation of the above + +### DUT BGP and Graceful Restart/ERR Configuration + +| Parameter | Value | Description | +| :--- | :--- | :--- | +| **BGP ASN** | `100` | The Autonomous System Number for the DUT's IBGP sessions. | +| **Hold Time** | 30 seconds | The BGP session hold timer. | +| **GR Restart Time** | 220 seconds | The time a peer should wait for the BGP session to re-establish during a graceful restart. | +| **GR Stale Routes Time** | 250 seconds | The duration for which a peer should hold stale routes during a graceful restart. | +| **ERR Enabled** | `True` | Extended Route Retention is enabled on the BGP peerings. | +| **ERR Retention Time** | 300 seconds | The time for which the DUT will hold stale routes under ExRR conditions. | +| **ERR Retention Policy** | `STALE-ROUTE-POLICY` | The policy applied to stale routes when ExRR is triggered. | + +### ATE Advertised Prefixes to the DUT + +**ATE:Port1 (IBGP Peer)** +| Prefix | Community | +| :--- | :--- | +| IPv4Prefix1 / IPv6Prefix1 | `NO-ERR` | +| IPv4Prefix2 / IPv6Prefix2 | `ERR-NO-DEPREF` | +| IPv4Prefix3 / IPv6Prefix3 | `TEST-IBGP` | + +**ATE:Port2 (EBGP Peer)** +| Prefix | Community | +| :--- | :--- | +| IPv4Prefix4 / IPv6Prefix4 | `NO-ERR` | +| IPv4Prefix5 / IPv6Prefix5 | `ERR-NO-DEPREF` | +| IPv4Prefix6 / IPv6Prefix6 | `TEST-EBGP` | + +--- +### DUT Routing Policies + +**`STALE-ROUTE-POLICY` (Applied during ERR)** +| Term Name | Match Condition | Action(s) | +| :--- | :--- | :--- | +| `no-retention` | Community `NO-ERR` | `REJECT` (Route is not retained under ERR) | +| `err-no-depref` | Community `ERR-NO-DEPREF` | `ACCEPT`; Add community `STALE` | +| `default-retention` | All other prefixes | `ACCEPT`; Set `local-preference` to 0; Add community `STALE` | + +**Standard BGP Policies (Applied during normal operation)** +| Policy Name | Direction | BGP Peer | Match Condition | Action(s) | +| :--- | :--- | :--- | :--- | :--- | +| `importibgp` | Import | IBGP | Community `testibgp` | Set `local-preference` to 200 | +| `exportibgp` | Export | IBGP | MED `50` | Set community `NEW-IBGP` | +| `importebgp` | Import | EBGP | Community `TEST-EBGP` | Set `MED` to 50 | +| `exportebgp` | Export | EBGP | Community `TESTIBGP` | Prepend AS-PATH with own ASN (100) twice; Set community `NEW-EBGP` | + +--- +### Test Traffic Flows for Verification + +| Flow Name | Source Prefix | Destination Prefix | +| :--- | :--- | :--- | +| Flow 1 | IPv4Prefix1 | IPv4Prefix4 | +| Flow 2 | IPv6Prefix1 | IPv6Prefix4 | +| Flow 3 | IPv4Prefix2 | IPv4Prefix5 | +| Flow 4 | IPv6Prefix2 | IPv6Prefix5 | +| Flow 5 | IPv4Prefix3 | IPv4Prefix6 | +| Flow 6 | IPv6Prefix3 | IPv6Prefix6 | + + +## Procedure + +## RT-1.35.1: Baseline Validation and Long-Duration Timer Configuration + +* Validate that BGP peers have exchanged graceful-restart capabilities + (capability 64) i.e. the "N" bit (the second most significant bit) is set. + Verify this on the ATEs. +* Verify that the restart-time = 220 Secs. Check this using OC data. +* Configure and validate a long-duration retention timer. In addition to the + 300-second timer used for the functional tests here, configure the ERR + retention-time to a large value \[minimum 180 days(15552000 seconds) but if + the vendor suppots larger value, pick the highest\] and + verify that the DUT accepts and reflects this value in its configuration + state. This validates the requirement for long-lived retention, even if + waiting for expiration is impractical for testing . Revert to 300 seconds + for subsequent tests. Validate the configurations using OC. + ``` + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/extended-route-retention/state/enabled + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/extended-route-retention/state/retention-time + ``` +* Validate ExRR retention-policy is set to "STALE-ROUTE-POLICY". + ``` + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/extended-route-retention/state/retention-policy` + ``` + a. Verify that all prefixes are learned correctly on the DUT. Check the AFT + entries for the same. On the ATEs, ensure that all the prefixes are learnt + with the correct initial BGP attributes (communities, local-preference, MED) + as per the import policies + + * IPv4Prefix1 and IPv6Prefix1 has community NO-ERR + * IPv4Prefix2 and IPv6Prefix2 has community ERR-NO-DEPREF + * IPv4Prefix3 and IPv6Prefix3 has community TEST-IBGP and has a + local-preference of 200 + * IPv4Prefix4 and IPv6Prefix4 has community NO-ERR + * IPv4Prefix5 and IPv6Prefix5 has community ERR-NO-DEPREF + * IPv4Prefix6 and IPv6Prefix6 has community TEST-EBGP and also has a MED + value of 50 + + b. On ATE:Port1, ensure the following received from DUT: + * IPv4Prefix4 and IPv6Prefix4 with community NO-ERR + * IPv4Prefix5 and IPv6Prefix5 with community ERR-NO-DEPREF + * IPv4Prefix6 and IPv6Prefix6 prefixes are received with a MED of 50 and has the + community TEST-EBGP and NEW-EBGP in that order. + + c. On ATE:Port2, ensure the following received from DUT: + * IPv4Prefix1 and IPv6Prefix1 has community NO-ERR + * IPv4Prefix2 and IPv6Prefix2 has community ERR-NO-DEPREF + * IPv4Prefix3 and IPv6Prefix3 has community TEST-IBGP and NEW-IBGP in that + order. Also, ensure that these prefixes have an AS-PATH of "100, 100, 100" + * Start traffic as per the Test flows above and ensure 100% success + + If any of the above verifications fail, then the test is a failure. + + ``` + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix + ``` + +## RT-1.35.2: DUT as Helper for a (gracefully) Restarting Peer + +* Trigger: From ATE:Port1 and ATE:Port2, gracefully terminate the BGP sessions + with the DUT (e.g., by sending a BGP Cease NOTIFICATION). +* Prevent the ATEs from re-establishing their BGP sessions for 280 + seconds. This duration is longer than the configured stale-routes-time + (250s) but shorter than the ExRR retention-time (300s), ensuring the DUT + transitions from standard Graceful Restart into the ExRR state. +* Verify traffic behavior: + * Prefixes with community NO-ERR (policy action REJECT) should + stop forwarding after the stale-routes-time (250s) expires. This + confirms the REJECT action in the ExRR policy removes the route + from being held. + * Prefixes with community ERR-NO-DEPREF should continue forwarding + and have the STALE community added. + * All other prefixes should continue forwarding, have their + local-preference set to 0, and have the STALE community added. +* Allow BGP to re-establish after 280 seconds and verify a return to the baseline state. +* Validation: Ensure the ATEs receive an End-of-RIB marker for the v4 and v6 peerings from the DUT after all routes are re-advertised. End-of-RIB marker is an empty BGP update message. + +## RT-1.35.3 ATE Peer Abrupt Termination (Hold-Timer Expiry) + +* Trigger: From ATE:Port1 and ATE:Port2, abruptly stop sending BGP messages, + including keepalives, to the DUT. This will cause the BGP hold-timer to + expire on the DUT. +* The DUT should enter Graceful Restart helper mode. Prevent the ATEs from + re-establishing their BGP sessions for 280 seconds. +* Verification: The expected behavior and traffic verification steps are + identical to the graceful termination test (RT-1.35.2) above, as the DUT + should trigger its ExRR logic once the stale-routes-time expires. + + +## RT-1.35.4: BGP Notification Handling (Graceful Teardown), "Administrative Reset" Notification (rfc4486) sent by the DUT + +`TODO: gNOI.ClearBGPNeighborRequest_GRACEFUL_RESET used in this case is under +review in https://github.com/openconfig/gnoi/pull/214` + +* Start traffic as per the flows above +* Trigger BGP Notification (code 6 subocde 4) from DUT:Port1 towards + ATE:Port1. Please use the `gNOI.ClearBGPNeighborRequest_GRACEFUL_RESET` + message. +* Cease notification of Code 6, subcode 4 will result in tcp connection reset + but the routes aren't flushed +* Configure ATE:Port1 to not send/accept any more TCP connections from the + DUT:Port1 until the "reset timer" on the DUT expires. +* Expected behavior is the same as RT-1.35.2 +* Revert ATE configuration to allow for the BGP sessions to be up. Restart + traffic and confirm that there is zero packet loss. Expected behavior is + same as the base test in RT-1.35.1 +* Restart the above procedure for the EBGP peering between DUT:Port-2 and + ATE:Port-2 + +## RT-1.35.5: BGP Notification Handling (Graceful Teardown), "Administrative Reset" Notification (rfc4486) received by the DUT + +`TODO: gNOI.ClearBGPNeighborRequest_GRACEFUL_RESET used in this case is under +review in https://github.com/openconfig/gnoi/pull/214` + +* Follow the same procedure as RT-1.35.6 above. However this time, Trigger BGP + Notification (code 6 subocde 4) from ATE:Port1 towards DUT:Port1. Please use + the `gNOI.ClearBGPNeighborRequest_GRACEFUL_RESET` message. +* Expected result is same as RT-1.35.2 above +* Revert ATE configurtion to allow for the BGP sessions to be up. Restart + traffic and confirm that there is zero packet loss. Expected behavior is + same as the base test in RT-1.35.1 +* Restart the above procedure for the EBGP peering between DUT:Port-2 and + ATE:Port-2 + +## RT-1.35.6: BGP Notification Handling (Hard Reset) sent by the DUT. + +`TODO: gNOI.ClearBGPNeighborRequest_HARD_RESET used in this case is under review +in https://github.com/openconfig/gnoi/pull/214` + +* Start traffic as per the flows above +* Trigger BGP "HARD RESET" Notification from the DUT:Port1 and DUT:Port2 + towards ATE:Port1 and ATE:Port2 respectively by using + `gNOI.ClearBGPNeighborRequest_HARD_RESET` message of the gNOI PROTO. +* As per + [rfc8538#section-3.1](https://datatracker.ietf.org/doc/html/rfc8538#section-3.1), + when "N bit" exchanged between peers (i.e. GR negotiated), the "HARD RESET" + notification of code 6 subcode 9 must be sent to the peer. However, the + subcode for "Administrative Reset" i.e. code 6 subcode 4 must be carried in + the data portion of subcode 9 notification message. +* On receipt of the "HARD RESET" Notification message from the DUT, the ATEs + MUST validate the Notification message and ensure it matches the bullet + above. +* Given the HARD_RESET received, the DUT must RESET the TCP and flush all + routes resulting in 100% packet loss for all the flows irrespective of the + ExRR configuration and the `STALE-ROUTE-POLICY`. The test MUST fail if this + isnt the behavior seen. Verficiation of packet loss done on the ATE side as + well as on the DUT using the following OC path. + `/interfaces/interface/state/counters/out-unicast-pkts` +* As soon as the BGP peering are up again between the ATEs and the DUT, + traffic flow must be successful and the expected behavior must be the same + as RT-1.35.1 + +## RT-1.35.7: BGP Notification Handling (Hard Reset) received by the DUT + +`TODO: gNOI.ClearBGPNeighborRequest_HARD_RESET used in this case is under review +in https://github.com/openconfig/gnoi/pull/214` + +* Start traffic as per the flows above +* Trigger BGP "HARD RESET" Notification from the ATE:Port1 to DUT:Port1 by + sending `gNOI.ClearBGPNeighborRequest_HARD` message to ATE:Port1. When this + happens and the DUT recieves BGP cease notification with subcode 9, the DUT + is expected to FLUSH all IBGP learnt routes irrespective of the ERR + configuration and therefore traffic between the flows will see 100% failure. +* Once the IBGP peering is reestablished, expected behavior is the same as + RT-1.35.1 +* Repeat the above process by sending gNOI.ClearBGPNeighborRequest_HARD to the + ATE:Port2. Expected behavior here is the same as seen for the IBGP peering. + +## RT-1.35.8: Additive Policy Application + +Verify that the ExRR retention policy is additive to the standard import policy. + +* In addition to the existing importibgp policy that sets local-preference to + 200, add an action to set the MED to 150 for prefixes with community + TEST-IBGP. +* In the baseline state, verify IPv4Prefix3 and IPv6Prefix3 have both + local-preference 200 and MED 150. Use the loc-rib state paths below. +* Trigger an ExRR state as in RT-1.35.2. The STALE-ROUTE-POLICY should apply a + local-preference of 0. Verify using the LOC-RIB path below. +* During the ExRR state, verify that the prefixes now have a local-preference + of 0 (from ExRR policy), a MED of 150 (from import policy), and the STALE + community (from ExRR policy). +* This confirms the retention policy does not override pre-existing attributes + set by other policies. If the behavior is different from this expectation + then fail the test. + +``` + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/ext-community-index + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/attr-index +``` + +## RT-1.35.9: Default Reject Behavior + +Verify that the default behavior is to drop stale routes when ExRR is enabled but +no policy is attached. + +* Enable ExRR on the DUT but do not configure a retention-policy. +* Trigger a BGP session failure on the ATEs that would normally activate ExRR +(e.g., as in RT-1.35.2). +* Verify that after the restart-timer expires, all routes from the failed +neighbor are flushed and traffic for all flows drops to 0%. Validate on the ATE +as well as using OC. +`/interfaces/interface/state/counters/out-unicast-pkts` +* Test Must fail if the default action without ExRR isn't satisfied. + + +## RT-1.35.10: Consecutive BGP Restarts + +Verify that ExRR is correctly triggered during rapid, consecutive session +failures. This test addresses the specific, complex failure scenario where +timers may not expire normally due to flapping. + +* Establish a baseline state with active traffic. +* Initiate a loop on the ATE peer: gracefully kill the BGP +process, wait for the session to re-establish with the DUT, and then immediately +kill it again. This cycle should be shorter than the DUT's stale-routes-time. +Check the BGP peering state on the ATEs as well as using OC. +`/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state` +* After several such cycles, stop the loop and prevent the ATE from +reconnecting. +* Verify that the DUT enters the ExRR state and holds the routes +according to the STALE-ROUTE-POLICY, as validated in RT-1.35.2. + + +## RT-1.35.11 + +Repeat the tests above, with ExRR configuration under the peer-group hierarchy. + +## Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "config": { + "neighbor-address": "192.168.1.1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "neighbor-address": "192.168.1.1" + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + } +} +``` + +## Canonical OpenConfig for ERR + +``` + { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "BGP", + "name": "BGP", + "bgp": { + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.168.1.1", + "graceful-restart": { + "config": { + "enabled": true + }, + "extended-route-retention": { + "config": { + "enabled": true, + "retention-time": 15552000, + "retention-policy": "STALE-ROUTE-POLICY" + } + } + } + } + ] + } + } + } + ] + } + } + ] + } +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + + + # BGP conifguration: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-group: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/neighbor-address: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/local-as: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/restart-time: + /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/stale-routes-time: + + # Telemetry Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/graceful-restart/state/advertised: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/peer-restart-time: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/graceful-restart/state/received: + /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/state/restart-time: + /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/state/stale-routes-time: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/community-index: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/restart-time: + # /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/graceful-restart/state/enabled: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/neighbors/neighbor/adj-rib-in-post/routes/route/path-id: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/ext-community-index: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/attr-index: + /interfaces/interface/state/counters/out-unicast-pkts: + + +rpcs: + gnmi: + gNMI.Set: + gNMI.Get: + gNMI.Subscribe: + gnoi: + system.System.KillProcess: + # bgp.ClearBGPNeighborRequest.Hard: +``` + + diff --git a/feature/bgp/gracefulrestart/otg_tests/exrr/metadata.textproto b/feature/bgp/gracefulrestart/otg_tests/exrr/metadata.textproto new file mode 100644 index 00000000000..7597315ce69 --- /dev/null +++ b/feature/bgp/gracefulrestart/otg_tests/exrr/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "79d56e4a-19af-4006-8920-a2ee0073f51c" +plan_id: "RT-1.35" +description: "BGP Graceful Restart Extended route retention (ExRR)" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md b/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md index 46466da8b60..92eb60b9aa5 100644 --- a/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md +++ b/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/README.md @@ -77,4 +77,4 @@ rpcs: ## Required DUT platform -* FFF \ No newline at end of file +* FFF diff --git a/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/metadata.textproto b/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/metadata.textproto new file mode 100644 index 00000000000..18ef1018b78 --- /dev/null +++ b/feature/bgp/linkbandwidth_community/otg_tests/linkbandwidth_aggregation/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1d9e8ec9-669c-493f-9952-2d4a30dec42e" +plan_id: "RT-7.6" +description: "BGP Link Bandwidth Community - Cumulative" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/bgp/multipath/feature.textproto b/feature/bgp/multipath/feature.textproto deleted file mode 100644 index af19a786e95..00000000000 --- a/feature/bgp/multipath/feature.textproto +++ /dev/null @@ -1,94 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_multipath" - version: 1 -} - -# Global BGP multipath leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/state/enabled" -} - -## ebgp multipath options leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/config/allow-multiple-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/config/maximum-paths" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/state/allow-multiple-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/state/maximum-paths" -} - -## ibgp multipath options leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ibgp/config/maximum-paths" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ibgp/state/maximum-paths" -} - -# Neighbor level BGP multipath options leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/state/enabled" -} - -# Peer group level BGP multipath leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/state/enabled" -} - -## ebgp multipath options leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ebgp/config/allow-multiple-as" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ebgp/config/maximum-paths" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ebgp/state/allow-multiple-as" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ebgp/state/maximum-paths" -} - -## ibgp multipath options leaves -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ibgp/config/maximum-paths" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/use-multiple-paths/ibgp/state/maximum-paths" -} - -feature_profile_dependency { - name: "bgp" - version: 1 -} diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto index 6a3d6250365..5a719650e6b 100644 --- a/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_ecmp_test/metadata.textproto @@ -44,6 +44,7 @@ platform_exceptions: { default_network_instance: "default" missing_value_for_defaults: true skip_setting_allow_multiple_as: true + enable_multipath_under_afi_safi: true } } tags: TAGS_DATACENTER_EDGE diff --git a/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/bgp_multipath_wecmp_test.go b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/bgp_multipath_wecmp_test.go index f6db0a2adab..862c34ef26f 100644 --- a/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/bgp_multipath_wecmp_test.go +++ b/feature/bgp/multipath/otg_tests/bgp_multipath_wecmp_lbw_community_test/bgp_multipath_wecmp_test.go @@ -210,21 +210,13 @@ func TestBGPSetup(t *testing.T) { helpers.GnmiCLIConfig(t, bs.DUT, communitySetCLIConfig) } } else { - if deviations.SkipSettingAllowMultipleAS(bs.DUT) { - bgp.GetOrCreateGlobal().GetOrCreateUseMultiplePaths().GetOrCreateEbgp().MaximumPaths = ygot.Uint32(maxPaths) - bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp().GetOrCreateLinkBandwidthExtCommunity().Enabled = ygot.Bool(true) - switch bs.DUT.Vendor() { - case ondatra.ARISTA: - helpers.GnmiCLIConfig(t, bs.DUT, "router bgp 65501\n ucmp mode 1\n") - default: - t.Fatalf("Unsupported vendor %s for deviation 'SkipSettingAllowMultipleAS'", bs.DUT.Vendor()) - } - } else { - gEBGP := bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + gEBGP := bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateUseMultiplePaths().GetOrCreateEbgp() + if !deviations.SkipSettingAllowMultipleAS(bs.DUT) { gEBGP.AllowMultipleAs = ygot.Bool(true) - gEBGP.MaximumPaths = ygot.Uint32(maxPaths) - gEBGP.GetOrCreateLinkBandwidthExtCommunity().Enabled = ygot.Bool(true) } + gEBGP.MaximumPaths = ygot.Uint32(maxPaths) + gEBGP.GetOrCreateLinkBandwidthExtCommunity().Enabled = ygot.Bool(true) + } configureOTG(t, bs) diff --git a/feature/bgp/otg_tests/afi_safi_oc_defaults/README.md b/feature/bgp/otg_tests/afi_safi_oc_defaults/README.md deleted file mode 100644 index c207ccf07ab..00000000000 --- a/feature/bgp/otg_tests/afi_safi_oc_defaults/README.md +++ /dev/null @@ -1,76 +0,0 @@ -Summary: - -- When operating in "openconfig mode", NOS (network operating system) defaults should match what OC defines as the defaults i.e, - - For BGP, there are no defaults for AFI-SAFI at the neighbor and peer-group levels. However at the global level the default is "false" -- This test currently only verifies the defaults for ipv4-unicast and ipv6-unicast families. However, this test can be extended further to cover for other - AFI-SAFIs as well in future. -- The test will check for default implementations under the neighbor and peer-group hierarchies and also test for inheritance rules as was specified in [pull/774](https://github.com/openconfig/public/pull/774) and [pull/815](https://github.com/openconfig/public/pull/815). - -Topology: -ATE (Port1) <-EBGP-> (Port1) DUT (Port2) <-IBGP-> (Port2) ATE - -- Connect ATE Port1 to DUT port1 (EBGP peering) -- Connect ATE Port2 to DUT port2 (IBGP peering) - -Procedure: - -- [Test case-1] AFI-SAFI configurations at "neighbor level": - - Push EBGP and IBGP OC configuration to the DUT - - Configuration should include corresponding IPv4 and IPv6 neighbor configurations. - - Ensure that only IPv4-Unicast enabled boolean is made "true" for IPv4 neighbor. "IPv6-unicast enabled" boolean is left to OC default for the IPv4 peer". - - Ensure that only IPv6-Unicast enabled boolean is made "true" for IPv6 neighbor. "IPv4-unicast enabled" boolean is left to OC default for the IPv6 peer". - - Ensure that there are no AFI-SAFI configurations at the global and peer-group levels. - - On the ATE side ensure that IPv4-unicast and IPv6-unicast AFI-SAFI are enabled==true for IPv4 and IPv6 neighbors. -- verification: - - For IPv4 neighbor, ensure that the IPv4 neighborship is up and IPv6-unicast capability is not negotiated. - - For IPv6 neighbor ensure that the IPv6 neighborship is up and IPv4-unicast capability is not negotiated. -- [Test case-2] IPv4-unicast and IPv6-Unicast AFI-SAFIs enabled at peer-group level: - - Configuration at the neighbor level is same as in [Test case-1] except for IPv4-unicast and IPv6-unicast being enabled at the peer-group level - - No configuration should be made at the global AFI-SAFI level - - verification: - - For IPv4 neighbor, ensure that the IPv4 neighborship is up and both IPv4-unicast and IPv6-unicast capabilities are negotiated. - - For IPv6 neighbor ensure that the IPv6 neighborship is up and both IPv4-unicast and IPv6-unicast capabilities are negotiated. -- [Test case-4] IPv4-unicast and IPv6-Unicast AFI-SAFIs enabled at Global level: - - Configuration at the neighbor level is same as in [Test case-1] except for IPv4-unicast and IPv6-unicast being enabled at the global level - - No configuration should be made at the peer-group AFI-SAFI level - - verification: - - For IPv4 neighbor, ensure that the IPv4 neighborship is up and both IPv4-unicast and IPv6-unicast capabilities are negotiated. - - For IPv6 neighbor ensure that the IPv6 neighborship is up and both IPv4-unicast and IPv6-unicast capabilities are negotiated. - -## OpenConfig Path and RPC Coverage - -```yaml - /network-instances/network-instance/protocols/protocol/bgp/global/config/as: - /network-instances/network-instance/protocols/protocol/bgp/global/config/router-id: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/auth-password: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/neighbor-address: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/neighbor-address: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/config/enabled: - - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/auth-password: - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/neighbor-address: - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/peer-as: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-group/peer-group-name: - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/config/enabled: - - /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/enabled: - -# Telemetry Parameter coverage - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-type: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-as: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/supported-capabilities: - - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/peer-type: - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/peer-as: - /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/state/local-as: - /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-group: - - rpcs: - gnmi: - gNMI.Set: - replace: true - gNMI.Subscribe: - on_change: true -``` diff --git a/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go index 0dfebdbe20d..9f3397aab18 100644 --- a/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/bgp_2byte_4byte_asn_policy_test.go @@ -30,6 +30,7 @@ import ( "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" ) @@ -38,12 +39,15 @@ const ( connExternal = "EXTERNAL" rejectPrefix = "REJECT-PREFIX" communitySet = "COMM-SET" + regexAsSet = "REGEX-AS-SET" rejectCommunity = "REJECT-COMMUNITY" rejectAspath = "REJECT-AS-PATH" aclStatement1 = "10" aclStatement2 = "20" aclStatement3 = "50" aclStatement4 = "60" + aclStatement5 = "70" + aclStatement6 = "80" prefixSubnetRangeV4 = "30..32" prefixSubnetRangeV6 = "126..128" globalAsNumber = 999 @@ -96,6 +100,10 @@ func TestBgpSession(t *testing.T) { dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dut.Port(t, "port1").Name(), deviations.DefaultNetworkInstance(dut), 0) + } + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() @@ -262,6 +270,9 @@ func configureRegexPolicy(t *testing.T, dut *ondatra.DUTDevice) { case ondatra.JUNIPER: config = juniperCLI() t.Logf("Push the CLI config:%s", dut.Vendor()) + default: + t.Logf("Push no CLI config:%s", dut.Vendor()) + return } gpbSetRequest, err := buildCliConfigRequest(config) @@ -328,6 +339,23 @@ func configureBGPPolicy(t *testing.T, d *oc.Root, isV4 bool, dut *ondatra.DUTDev } stmt60.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateAsPathSet(regexAsSet).SetAsPathSetMember([]string{".* 4400 3300"}) + pdefAs := rp.GetOrCreatePolicyDefinition(rejectAspath) + + stmt70, err := pdefAs.AppendNewStatement(aclStatement5) + if err != nil { + t.Errorf("Error while creating new statement %v", err) + } + stmt70.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE + stmt70.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetAsPathSet(regexAsSet) + stmt70.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) + + stmt80, err := pdefAs.AppendNewStatement(aclStatement6) + if err != nil { + t.Errorf("Error while creating new statement %v", err) + } + stmt80.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + return rp } @@ -348,7 +376,12 @@ func verifyPrefixesTelemetryV4(t *testing.T, dut *ondatra.DUTDevice, wantInstall statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() prefixesv4 := statePath.Neighbor(ateSrc.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes() - if gotInstalled := gnmi.Get(t, dut, prefixesv4.Installed().State()); gotInstalled != wantInstalled { + gotInstalled, ok := gnmi.Watch(t, dut, prefixesv4.Installed().State(), 15*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotInstalled, _ := val.Val() + return gotInstalled == wantInstalled + }).Await(t) + + if !ok { t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, wantInstalled) } } @@ -359,7 +392,12 @@ func verifyPrefixesTelemetryV6(t *testing.T, dut *ondatra.DUTDevice, wantInstall statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() prefixesv6 := statePath.Neighbor(ateSrc.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Prefixes() - if gotInstalledv6 := gnmi.Get(t, dut, prefixesv6.Installed().State()); gotInstalledv6 != wantInstalledv6 { + gotInstalledv6, ok := gnmi.Watch(t, dut, prefixesv6.Installed().State(), 15*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotInstalledv6, _ := val.Val() + return gotInstalledv6 == wantInstalledv6 + }).Await(t) + + if !ok { t.Errorf("IPV6 Installed prefixes mismatch: got %v, want %v", gotInstalledv6, wantInstalledv6) } } @@ -554,6 +592,9 @@ func createBgpNeighbor(nbr *bgpNbr, dut *ondatra.DUTDevice) *oc.NetworkInstance_ global.As = ygot.Uint32(globalAsNumber) global.RouterId = ygot.String(dutSrc.IPv4) + bgpGlobalIPv4AF := global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + bgpGlobalIPv4AF.SetEnabled(true) + pg := bgp.GetOrCreatePeerGroup("ATE") pg.PeerAs = ygot.Uint32(nbr.peerAS) pg.PeerGroupName = ygot.String("ATE") diff --git a/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto index 76674ae49d8..2ad331aefea 100644 --- a/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_2byte_4byte_asn_policy_test/metadata.textproto @@ -21,7 +21,6 @@ platform_exceptions: { deviations: { explicit_interface_in_default_vrf: true interface_enabled: true - bgp_conditions_match_community_set_unsupported: true } } platform_exceptions: { diff --git a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go index a73944223ce..500658c985b 100644 --- a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/bgp_override_as_path_split_horizon_test.go @@ -293,11 +293,7 @@ func testSplitHorizonNoAllowOwnIn(t *testing.T, args *otgTestArgs) { func testSplitHorizonAllowOwnAs1(t *testing.T, args *otgTestArgs) { t.Log("Test allow-own-as 1, Enable allow-own-as 1 on the DUT.") dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - if deviations.BgpAllowownasDiffDefaultValue(args.dut) { - gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 2) - } else { - gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 1) - } + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 1) t.Log("Re-advertise the prefix from the ATE with the same AS-path.") advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) @@ -319,11 +315,7 @@ func testSplitHorizonAllowOwnAs1(t *testing.T, args *otgTestArgs) { func testSplitHorizonAllowOwnAs3(t *testing.T, args *otgTestArgs) { t.Log("Test allow-own-as 3, Enable allow-own-as 3 on the DUT.") dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - if deviations.BgpAllowownasDiffDefaultValue(args.dut) { - gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 4) - } else { - gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 3) - } + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 3) t.Run("Re-advertise the prefix from the ATE with 1 Occurrence: 65500 dutLocalAS1 65499", func(t *testing.T) { advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) @@ -373,11 +365,7 @@ func testSplitHorizonAllowOwnAs3(t *testing.T, args *otgTestArgs) { func testSplitHorizonAllowOwnAs4(t *testing.T, args *otgTestArgs) { t.Log("Test allow-own-as 4, Enable allow-own-as 4 on the DUT.") dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(args.dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - if deviations.BgpAllowownasDiffDefaultValue(args.dut) { - gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 5) - } else { - gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 4) - } + gnmi.Replace(t, args.dut, dutConfPath.Bgp().PeerGroup(peerGrpName1).AsPathOptions().AllowOwnAs().Config(), 4) t.Run("Re-advertise the prefix from the ATE with 1 Occurrence: 65500, dutLocalAS1, 65499", func(t *testing.T) { advBGPRouteFromOTG(t, args, []uint32{65500, dutLocalAS1, 65499}) diff --git a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto index f1f229cf639..b2a3468dc67 100644 --- a/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_override_as_path_split_horizon_test/metadata.textproto @@ -25,12 +25,4 @@ platform_exceptions: { bgp_set_med_requires_equal_ospf_set_metric: true } } -platform_exceptions: { - platform: { - vendor: JUNIPER - } - deviations: { - bgp_allowownas_diff_default_value: true - } -} tags: TAGS_AGGREGATION diff --git a/feature/bgp/otg_tests/bgp_scale_test/README.md b/feature/bgp/otg_tests/bgp_scale_test/README.md new file mode 100644 index 00000000000..1f4c7b0f7e4 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_scale_test/README.md @@ -0,0 +1,2013 @@ +# RT-1.65: BGP scale test + +## Summary + +This test suite should ensure that the router will be able to handle below BGP +scale: + +* 200-1500 v4/v6 EBGP sessions +* 7-31 v4/v6 IBGP sessions +* 7-31 ISIS adjacencies +* v4/v6 IBGP transport addresses received via ISIS +* 60000 x ISIS prefixes +* 1.5M IPv4 BGP prefixes; the routes are distributed across /22[20%], /25[30%], /24[20%] and /30[30%] prefix lengths +* 60k x IPv6 prefixes; the routes are distributed across /48[40%] , /64[40%], /126[20%] prefix lengths +* /25 routes are advertised with NO-ADVERTISE and 100:100 communities and redistributed into ISIS +* multiple features enabled to ensure interaction between different features under scale conditions: + - ebgp multipath: 64 maximum-paths + - change default ebgp administrative distance + - Graceful restart enabled + - Default Hold/keepalive timers modified + - Link bandwidth + +Different scale tests to validate the router performance under different +conditions e.g steady state (no route flaps, low scale), v4/v6 EBGP sessions +scale (2 Bytes/4 Bytes AS), v4/v6 EBGP sessions and routes scale and validation +of DUT under scale condition and dynamic events; e.g routes churn, BGP peer +reset, routes withdraw/re-advertise, Altering BGP attributes. + +### Testbed type + +[TESTBED_DUT_ATE_4LINKS](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +### Topology + +```mermaid +graph LR + A[ATE PORT1] <-- IPv4-IPv6 Traffic And BGP Convergence Tests --> B[DUT PORT1]; + C[ATE:PORT2] <-- 200 V4/V6 eBGP Peers--> D[DUT PORT2 VLAN11 to VLAN210]; + E[ATE:PORT3] <-- 200 V4/V6 eBGP Peers--> F[DUT PORT3 VLAN21 to VLAN220]; + G[ATE:PORT4] <-- 7/15/31 ISIS Adj + 7/15/31 V4/V6 IBGP --> H[DUT PORT4 VLAN31 to VLAN34/VLAN45/VLAN61 ]; +``` + +### DUT and OTG properties + + +Device | Port4 IPv4 | Port4 IPv6 | ISIS AREA | ISIS ID | ROUTER ID +:------ | :-------------- | :------------- | :-------- | :--------: | :---------: +DUT | 200.1.[1-31].1/30 | 1000:4:[31-61]::200::0:1/126 | 49001 | 1920.0000.2001 | 200.1.1.200 +ATE | 200.1.[1-31].2/30 | 1000:4:[31-61]::200::0:2/126 | 49001 | 64000000000[1-31] | 203.0.113.[1-31] + + + +### Policy Definition: + + * Configure the following community set on the DUT. + * Create a community-set named `ebgp-routes` with members as follows: + * `{ community-member = [ "100:101", "100:102", "100:103", "100:104", "100:105", "100:106", "100:107" ] }` + * Create a community-set named `SNH` with members as follows: + * `{ community-member = [ "100:100" ] }` + + * Create a `policy-definition` named 'IBGP-IN'/'IBGP-OUT' with the following `statements` + * statement[name='accept_ebgp']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'ebgp-routes' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + * statement[name='reject_all']/ + * actions/config/policy-result = REJECT_ROUTE + * Create a `policy-definition` named 'ALLOW' with the following `statements` + * statement[name='accept_all']/ + * actions/config/policy-result = ACCEPT_ROUTE + * Create a `policy-definition` named 'BGP_TO_ISIS' with the following `statements` + * statement[name='snh']/ + * conditions/bgp-conditions/match-community-set/config/community-set = 'SNH' + * conditions/bgp-conditions/match-community-set/config/match-set-options = ANY + * actions/config/policy-result = ACCEPT_ROUTE + * statement[name='reject_all']/ + * actions/config/policy-result = REJECT_ROUTE + +### RT-1.65.1: (Initial State 200 v4/v6 EBGP sessions, 2 Byte AS, 7 v4/v6 IBGP sessions, 7 ISIS adjacencies, no routes scale ): + +* Verification (Telemetry): + * Verify that all LAG port UP and all isis adjacencies are UP + * Verify that all EBGP/IBGP sessions established + * Verify that all configured BGP capabilities are negotiated + * Verify that no process restarted + * Verify that no core file generated + * Measure BGP convergence for both V4/V6 unicast AFI/SAFI + +### RT-1.65.2: (1500 v4/v6 EBGP sessions, 2Byte/4Byte ASs, 31 v4/v6 IBGP sessions, 31 ISIS adjacencies, no routes scale ): + +* Modify DUT Setup to support 1500 v4/v6 EBGP sessions, 2Byte/4Byte ASs, 31 v4/v6 IBGP sessions, 31 ISIS adjacencies and no routes scale + +* Verification (Telemetry): + * Verify that all LAG port UP and all isis adjacencies are UP + * Verify that all EBGP/IBGP sessions established + * Verify that all configured BGP capabilities are negotiated + * Verify that no process restarted + * Verify that no core file generated + * Measure BGP convergence for both V4/V6 unicast AFI/SAFI + +### RT-1.65.3: (1500 v4/v6 EBGP sessions, 2Byte/4Byte ASs, 31 v4/v6 IBGP sessions, 31 ISIS adjacencies, v4 and v6 routes scale): + +* Modify DUT Setup to support 1500 v4/v6 EBGP sessions, 2 Byte/4 Byte ASs, 31 v4/v6 IBGP sessions, 31 ISIS adjacencies and V4/V6 routes scale + +* Verification (Telemetry): + * Verify that all LAG port UP and all isis adjacencies are UP + * Verify that all EBGP/IBGP sessions established + * Verify that all configured BGP capabilities are negotiated + * Verify that no process restarted + * Verify that no core file generated + * Measure BGP convergence for both V4/V6 unicast AFI/SAFI + +### RT-1.65.4: RT-1.65.3 setup + Route churn + BGP attributes modification + BGP peers reset + Routes withdrawal and re-advertise + +* Same Setup as RT-1.65.3 +* RT-1.65.4.1: Multiple iterations to reset all BGP sessions +* RT-1.65.4.2: Multiple iterations to alter BGP attributes: Local-Preference, AS-Path length, ORIGIN +* RT-1.65.4.3: introduce routes churn: multiple iteration to withdraw and re-advertise routes + +* Verification (Telemetry): + * Verify that all LAG port UP and all isis adjacencies are UP + * Verify that all EBGP/IBGP sessions established + * Verify that all configured BGP capabilities are negotiated + * Verify that no process restarted + * Verify that no core file generated + * Measure BGP convergence for both V4/V6 unicast AFI/SAFI + +### Procedure + +#### DUT Setup: + +* Connect DUT port-1, port-2, port-3 and port-4 to OTG port-1, port-2, port-3 and port-4 respectively +* Configure OTG : + 1. Create aggregate interface with LACP enabled + 2. Create emulated router with Port4 ethernet interface + 3. Add Port4 as member of AGG created in the step.1 + 4. Configure Subinterfaces to emulate 7/31 ISIS adjacencies + 5. Create Loopback and associate to each sub-interface + 6. Configure ISIS on each subinterface + * Set ISIS System ID + * Set ISIS area ID + * Enable wide metrics + * Enable ISIS on the ethernet interface with Level 2 only and circuit type of point to point + * Create NET to advertise P2P links, /25, /64 prefixes into ISIS + 7. Configure EBGP session on all sub-interfaces that belong to Ports 1-3 + Number of SubInterfaces is part of Port's attributes to define + the target scale. Below steps are repeated for each subInterface: + * Set Router ID + * Set EBGP session Type + * Enable Graceful Restart + * Enable Hold/Keepalive Timers + * Enable IPv4/v6 AFI/SAFI + * Set Peer Address + * Enable learning routes from DUT for EBGP session used to measure the convergence + * Create NET to advertise EBGP routes: /30, /22, /25, /64, 126 prefixes. BGP attributes will be associated to each of the routes + 8. Configure IBGP session on all loopbacks addresses advertised via ISIS: + * Set Router ID + * Set IBGP session Type + * Enable Graceful Restart + * Enable Hold/Keepalive Timers + * Enable IPv4/v6 AFI/SAFI + * Set Peer Address (DUT loopback) + * Create NET to advertise IBGP routes: /22, /25, /64 prefixes. BGP attributes will be associated to each of the routes + + 9. Create IPv4 Flows from Port1 to `ipv4Traffic` list generated as part of prefixes advertised via BGP + * 172.30.252.0 - 172.30.252.100 + tcp ports 12345 - 12545 + * 172.31.252.0 - 172.31.252.100 + tcp ports 12345 - 12545 + * 173.30.252.0 - 173.30.252.100 + tcp ports 12345 - 12545 + * 173.31.252.0 - 173.31.252.100 + tcp ports 12345 - 12545 + + + 10. Create IPv6 Flows from Agg2 to the below ranges + * 2001:db8:2:118::1 - 2001:db8:2:118::64 + tcp ports 12345 - 12545 + * 2001:db8:2:119::1 - 2001:db8:2:119::64 + tcp ports 12345 - 12545 + * 2001:db8:3:118::1 - 2001:db8:3:118::64 + tcp ports 12345 - 12545 + * 2001:db8:3:119::1 - 2001:db8:3:119::64 + tcp ports 12345 - 12545 + 11. Push configuration to OTG +* Configure DUT: + 1. Configure the DUT loopback and push path to DUT. this step will handle cases where DUT is already provisioned with a Loopback + 2. Configure the default network instance and push path to DUT + 3. Configure BGP policies: + * IBGB-IN/IBGP-OUT policies for IBGP sessions + * ALLOW policy for EBGP session + 4. Create an LAG interface set the interface IP address and LACP and push path to DUT + 5. Create a LAG's member. + 6. Create SubInterfaces to scale v4/v6 EBGP sessions + 7. Configure ISIS for DUT : + * Create ISIS instance under the default routing instance + * Set ISIS net address (area ID + System ID) + * Set ISIS level capability to level 2 only + * Set metric style to wide + * Enable IPv4-unicast and IPv6-unicast address families. + * Enable ISIS on the Loopback and the LAG's subInterfaces (7/31). + * Create BGP_TO_ISIS policy + * Export policy to redistribute EBGP routes tagged with communities 100:100/NO-ADVERTISE into ISIS. those /25 routes (~500 /25 prefixes) summarize PNH used to recursive lookup BGP routes. + * Push the configuration to the DUT. + 8. Configure BGP for DUT : + * Create BGP instance under the default routing instance + * Set Router ID + * Set DUT global AS + * Enable EBGP AllowMultipathAS + * Enable GracefulRestart and set RestartTimer/StaleRouteTimer + * Enable V4/V6 Uncast AFI/SAFI families + * Enable EBGP administrative distance/Route Preference + * Set EBGP maximum-paths to 64 + * Create EBGP/IBGP groups: 2 IBGP groups and set of EBGP groups + depending on EBGP scale + Every 4 subinterfaces will belong to same EBGP group and share + same properties and will receive same set of EBGP routes + * Set BGP Peer AS (both 2 Bytes and 4 bytes will be considered) + * Set Import/Export policy per BGP group's type (EBGP vs IBGP) + * Set BGP group's description + * Enable SendCommunity + * Set Transport Address for v4/v6 IBGP groups + * link BGP peers to associated BGP groups +* Start protocols on OTG +* Wait for 1 min then confirm that DUT has: + 1. 7/31 ISIS adjacencies up. + 2. 200/1500 EBGP session are up. + 3. 7/31 IBGP session are up. + +#### Testing steps + +* Measure IPv4/v6 BGP Convergence +* Collect the below states from the DUT : + 1. Count of UP ISIS adjacencies + 2. Count of UP V4 BGP sessions + 3. Count of UP V6 BGP sessions + 4. Count of BGP IPv4 routes advertised to Port1 + 5. Count of BGP IPv6 routes advertised to Port1 +* Start traffic flows +* Wait for 2 mins +* Stop traffic flows +* Collect the number of RX and TX traffic flows packets + +#### Test pass fail criteria + +* If any of the below conditions are not met test should be declared as a "FAIL" + 1. Count of ISIS adjacencies in the "UP" state is less than 7/31 + 2. Count of any of the below metrics was not equal to the below values : + a. v4 BGP sessions is less than 208/1532 + (The number of v4 BGP sessions is the sum of eBGP sessions [200/1500], iBGP sessions [7/31], and one session for + convergence measurement) + b. v6 BGP sessions is less than 208/1532 + c. BGP capabilities successfully negotiated + d. BGP routes successfully reflected to Port1's v4/v6 EBGP sessions + 3. Number of TX packets is not equal to RX packets. + 4. No critical process (RPD, Bgp-main, BGP, sr_bgp_mgr) restarted during the test + +## Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "global": { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 64 + } + } + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + }, + "use-multiple-paths": { + "ebgp": { + "config": { + "maximum-paths": 64 + } + } + } + } + ] + }, + "config": { + "as": 64500, + "router-id": "203.1.113.200" + }, + "default-route-distance": { + "config": { + "external-route-distance": 9 + } + }, + "graceful-restart": { + "config": { + "enabled": true, + "restart-time": 120, + "stale-routes-time": 300 + } + } + }, + "neighbors": { + "neighbor": [ + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:1:11:0:50:1:1:2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V62-1" + }, + "neighbor-address": "1000:1:11:0:50:1:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:1:12:0:50:1:1:2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V62-1" + }, + "neighbor-address": "1000:1:12:0:50:1:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:1:13:0:50:1:1:2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V62-1" + }, + "neighbor-address": "1000:1:13:0:50:1:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:1:14:0:50:1:1:2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V62-1" + }, + "neighbor-address": "1000:1:14:0:50:1:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:1:15:0:50:1:1:2", + "peer-as": 800201, + "peer-group": "BGP-PEER-GROUP-V62-2" + }, + "neighbor-address": "1000:1:15:0:50:1:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:2:21:0:50:2:1:2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V63-1" + }, + "neighbor-address": "1000:2:21:0:50:2:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:2:22:0:50:2:1:2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V63-1" + }, + "neighbor-address": "1000:2:22:0:50:2:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:2:23:0:50:2:1:2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V63-1" + }, + "neighbor-address": "1000:2:23:0:50:2:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:2:24:0:50:2:1:2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V63-1" + }, + "neighbor-address": "1000:2:24:0:50:2:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "1000:2:25:0:50:2:1:2", + "peer-as": 800202, + "peer-group": "BGP-PEER-GROUP-V63-2" + }, + "neighbor-address": "1000:2:25:0:50:2:1:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "192.0.2.2", + "peer-as": 800100, + "peer-group": "BGP-PEER-GROUP-V41" + }, + "neighbor-address": "192.0.2.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "2001:db8::192:0:2:2", + "peer-as": 800100, + "peer-group": "BGP-PEER-GROUP-V61" + }, + "neighbor-address": "2001:db8::192:0:2:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "IBGP-OUT" + ], + "import-policy": [ + "IBGP-IN" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "2001:db8::203:0:113:1", + "peer-as": 64500, + "peer-group": "BGP-RR-GROUP-V6" + }, + "neighbor-address": "2001:db8::203:0:113:1" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "IBGP-OUT" + ], + "import-policy": [ + "IBGP-IN" + ] + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "2001:db8::203:0:113:2", + "peer-as": 64500, + "peer-group": "BGP-RR-GROUP-V6" + }, + "neighbor-address": "2001:db8::203:0:113:2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "IBGP-OUT" + ], + "import-policy": [ + "IBGP-IN" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "203.0.113.1", + "peer-as": 64500, + "peer-group": "BGP-RR-GROUP-V4" + }, + "neighbor-address": "203.0.113.1" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "IBGP-OUT" + ], + "import-policy": [ + "IBGP-IN" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "203.0.113.2", + "peer-as": 64500, + "peer-group": "BGP-RR-GROUP-V4" + }, + "neighbor-address": "203.0.113.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.1.1.2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V42-1" + }, + "neighbor-address": "50.1.1.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.1.2.2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V42-1" + }, + "neighbor-address": "50.1.2.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.1.3.2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V42-1" + }, + "neighbor-address": "50.1.3.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.1.4.2", + "peer-as": 800101, + "peer-group": "BGP-PEER-GROUP-V42-1" + }, + "neighbor-address": "50.1.4.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.1.5.2", + "peer-as": 800201, + "peer-group": "BGP-PEER-GROUP-V42-2" + }, + "neighbor-address": "50.1.5.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.2.1.2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V43-1" + }, + "neighbor-address": "50.2.1.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.2.2.2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V43-1" + }, + "neighbor-address": "50.2.2.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.2.3.2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V43-1" + }, + "neighbor-address": "50.2.3.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.2.4.2", + "peer-as": 800102, + "peer-group": "BGP-PEER-GROUP-V43-1" + }, + "neighbor-address": "50.2.4.2" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "enabled": true, + "neighbor-address": "50.2.5.2", + "peer-as": 800202, + "peer-group": "BGP-PEER-GROUP-V43-2" + }, + "neighbor-address": "50.2.5.2" + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V41", + "peer-as": 800100, + "peer-group-name": "BGP-PEER-GROUP-V41" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V41", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V42-1", + "peer-as": 800101, + "peer-group-name": "BGP-PEER-GROUP-V42-1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V42-1", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V42-2", + "peer-as": 800201, + "peer-group-name": "BGP-PEER-GROUP-V42-2" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V42-2", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V43-1", + "peer-as": 800102, + "peer-group-name": "BGP-PEER-GROUP-V43-1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V43-1", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V43-2", + "peer-as": 800202, + "peer-group-name": "BGP-PEER-GROUP-V43-2" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V43-2", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V44-1", + "peer-as": 800103, + "peer-group-name": "BGP-PEER-GROUP-V44-1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V44-1", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V61", + "peer-as": 800100, + "peer-group-name": "BGP-PEER-GROUP-V61" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V61", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V62-1", + "peer-as": 800101, + "peer-group-name": "BGP-PEER-GROUP-V62-1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V62-1", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V62-2", + "peer-as": 800201, + "peer-group-name": "BGP-PEER-GROUP-V62-2" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V62-2", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V63-1", + "peer-as": 800102, + "peer-group-name": "BGP-PEER-GROUP-V63-1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V63-1", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V63-2", + "peer-as": 800202, + "peer-group-name": "BGP-PEER-GROUP-V63-2" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V63-2", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-PEER-GROUP-V64-1", + "peer-as": 800103, + "peer-group-name": "BGP-PEER-GROUP-V64-1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-PEER-GROUP-V64-1", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "BGP-RR-GROUP-V4", + "peer-as": 64500, + "peer-group-name": "BGP-RR-GROUP-V4" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-RR-GROUP-V4", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + }, + "transport": { + "config": { + "local-address": "203.1.113.200" + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "BGP-RR-GROUP-V6", + "peer-as": 64500, + "peer-group-name": "BGP-RR-GROUP-V6" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "BGP-RR-GROUP-V6", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + }, + "transport": { + "config": { + "local-address": "2001:1:db8:0:203:0:113:200" + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": true + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": false + } + } + ] + }, + "config": { + "description": "Port4LoopV4", + "peer-as": 64500, + "peer-group-name": "Port4LoopV4" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "Port4LoopV4", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + }, + "transport": { + "config": { + "local-address": "203.1.113.200" + } + } + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "config": { + "afi-safi-name": "IPV4_UNICAST", + "enabled": false + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "config": { + "afi-safi-name": "IPV6_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "description": "Port4LoopV6", + "peer-as": 64500, + "peer-group-name": "Port4LoopV6" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "peer-group-name": "Port4LoopV6", + "timers": { + "config": { + "hold-time": 240, + "keepalive-interval": 80, + "minimum-advertisement-interval": 30 + } + }, + "transport": { + "config": { + "local-address": "2001:1:db8:0:203:0:113:200" + } + } + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + }, + "routing-policy": { + "policy-definitions": { + "policy-definition": [ + { + "config": { + "name": "ALLOW" + }, + "name": "ALLOW" + }, + { + "config": { + "name": "IBGP-IN" + }, + "name": "IBGP-IN" + }, + { + "config": { + "name": "IBGP-OUT" + }, + "name": "IBGP-OUT" + } + ] + } + } +} +``` + +## OpenConfig Path and RPC Coverage +```yaml +paths: + + ## Config paths + + /network-instances/network-instance/protocols/protocol/bgp/global/config/as: + /network-instances/network-instance/protocols/protocol/bgp/global/config/router-id: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/minimum-advertisement-interval: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/minimum-advertisement-interval: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/hold-time: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/keepalive-interval: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/config/local-address: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/neighbor-address: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-as: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/neighbor-address: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/peer-as: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/global/default-route-distance/config/external-route-distance: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/graceful-restart/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/graceful-restart/config/helper-only: + /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/restart-time: + /network-instances/network-instance/protocols/protocol/bgp/global/graceful-restart/config/stale-routes-time: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/link-bandwidth-ext-community/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ibgp/link-bandwidth-ext-community/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/config/enabled: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/use-multiple-paths/ebgp/config/allow-multiple-as: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/use-multiple-paths/ebgp/config/maximum-paths: + + /network-instances/network-instance/table-connections/table-connection/config/address-family: + /network-instances/network-instance/table-connections/table-connection/config/src-protocol: + /network-instances/network-instance/table-connections/table-connection/config/dst-protocol: + /network-instances/network-instance/table-connections/table-connection/config/disable-metric-propagation: + /network-instances/network-instance/table-connections/table-connection/config/import-policy: + /routing-policy/policy-definitions/policy-definition/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/config/name: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member: + /routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set/config/community-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set/config/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy: + + ## Telemetry State paths + /interfaces/interface/state/admin-status: + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/supported-capabilities: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: + /system/processes/process/state/cpu-utilization: + /system/processes/process/state/memory-utilization: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/bgp/otg_tests/bgp_scale_test/metadata.textproto b/feature/bgp/otg_tests/bgp_scale_test/metadata.textproto new file mode 100644 index 00000000000..6bc822aa857 --- /dev/null +++ b/feature/bgp/otg_tests/bgp_scale_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c4998950-3e64-4aa9-bff8-7df0392787a1" +plan_id: "RT-1.65" +description: "BGP scale test" +testbed: TESTBED_DUT_ATE_4LINKS diff --git a/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md index 8c21d568b0e..c66da3f7e21 100644 --- a/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md +++ b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/README.md @@ -42,6 +42,11 @@ rpcs: gNMI.Set: ``` +## Canonical OC +```json +{} +``` + ## Minimum DUT platform requirement N/A diff --git a/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/bgp_tcp_mss_path_mtu_test.go b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/bgp_tcp_mss_path_mtu_test.go index 58975462e5f..61ab8e8cf11 100644 --- a/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/bgp_tcp_mss_path_mtu_test.go +++ b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/bgp_tcp_mss_path_mtu_test.go @@ -67,6 +67,7 @@ const ( var ( dut1Port1 = attrs.Attributes{ Desc: "DUT to ATE source", + Name: "port1", IPv4: "192.0.2.1", IPv6: "2001:db8::192:0:2:1", IPv4Len: plenIPv4, @@ -81,12 +82,13 @@ var ( IPv6Len: plenIPv6, } dut1Port2 = attrs.Attributes{ + Name: "port2", Desc: "DUT1 to DUT2", IPv4: "192.0.2.5", IPv4Len: plenIPv4, } dut2Port1 = attrs.Attributes{ - Name: "DUT2 to DUT1", + Name: "port1", IPv4: "192.0.2.6", IPv4Len: plenIPv4, } @@ -95,18 +97,29 @@ var ( // configureDUT configures all the interfaces on the DUT. func configureDUT(t *testing.T) { dc := gnmi.OC() + dut1 := ondatra.DUT(t, "dut1") dut2 := ondatra.DUT(t, "dut2") - t.Log("Configure interfaces on dut1.") - i1 := dut1Port1.NewOCInterface(dut1.Port(t, "port1").Name(), dut1) - gnmi.Replace(t, dut1, dc.Interface(i1.GetName()).Config(), i1) - i2 := dut1Port2.NewOCInterface(dut1.Port(t, "port2").Name(), dut1) - gnmi.Replace(t, dut1, dc.Interface(i2.GetName()).Config(), i2) - - t.Log("Configure interfaces on dut2.") - i3 := dut2Port1.NewOCInterface(dut2.Port(t, "port1").Name(), dut2) - gnmi.Replace(t, dut2, dc.Interface(i3.GetName()).Config(), i3) + dutPortsMap := map[*ondatra.DUTDevice][]*attrs.Attributes{ + dut1: {&dut1Port1, &dut1Port2}, + dut2: {&dut2Port1}, + } + t.Log("Configure interfaces on dut.") + for dutx, dutports := range dutPortsMap { + for _, portx := range dutports { + port := dutx.Port(t, portx.Name) + dutInt := portx.NewOCInterface(port.Name(), dutx) + ethPort := dutInt.GetOrCreateEthernet() + if deviations.FrBreakoutFix(dut1) && port.PMD() == ondatra.PMD100GBASEFR { + ethPort.SetAutoNegotiate(false) + ethPort.SetDuplexMode(oc.Ethernet_DuplexMode_FULL) + ethPort.SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB) + } + dutInt.SetType(oc.IETFInterfaces_InterfaceType_ethernetCsmacd) + gnmi.Replace(t, dutx, dc.Interface(dutInt.GetName()).Config(), dutInt) + } + } } // bgpCreateNbr creates bgp configuration on dut device. @@ -209,12 +222,12 @@ func configureISIS(t *testing.T, dut *ondatra.DUTDevice, intfName []string, dutA gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Config(), prot) } -func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { +func configureOTG(t *testing.T, otg *otg.OTG, mtu uint32) gosnappi.Config { config := gosnappi.NewConfig() port1 := config.Ports().Add().SetName("port1") iDut1Dev := config.Devices().Add().SetName(atePort1.Name) - iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC).SetMtu(mtu) iDut1Eth.Connection().SetPortName(port1.Name()) iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dut1Port1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) @@ -243,12 +256,12 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { return config } -func configOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { +func configOTG(t *testing.T, otg *otg.OTG, mtu uint32) gosnappi.Config { config := gosnappi.NewConfig() port1 := config.Ports().Add().SetName("port1") iDut1Dev := config.Devices().Add().SetName(atePort1.Name) - iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC).SetMtu(uint32(mtu5040B)) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC).SetMtu(mtu) iDut1Eth.Connection().SetPortName(port1.Name()) iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") iDut1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dut1Port1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) @@ -358,7 +371,7 @@ func TestTcpMssPathMtu(t *testing.T) { var otgConfig gosnappi.Config t.Run("Configure OTG", func(t *testing.T) { - otgConfig = configureOTG(t, otg) + otgConfig = configureOTG(t, otg, uint32(mtu5040B)) }) var dut1NbrIP = []string{atePort1.IPv4, atePort1.IPv6} @@ -378,7 +391,7 @@ func TestTcpMssPathMtu(t *testing.T) { } }) } - t.Run("Change the Interface MTU to the ATE port as 5040.", func(t *testing.T) { + t.Run("Change the Interface MTU to the DUT1 port as 5040.", func(t *testing.T) { t.Logf("Configure DUT1 interface MTU to %v", mtu5040B) configureIntfMTU(t, dut1, dut1.Port(t, "port1"), mtu5040B) @@ -435,7 +448,7 @@ func TestTcpMssPathMtu(t *testing.T) { t.Run("Configure iBGP session ATE Port1 - DUT2", func(t *testing.T) { otg.StopProtocols(t) - otgConfig = configOTG(t, otg) + otgConfig = configOTG(t, otg, uint32(mtu5040B)) }) t.Run("Verify iBGP session between DUT2 - ATE Port1.", func(t *testing.T) { diff --git a/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto index ff417e46546..37e5de2a69e 100644 --- a/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto +++ b/feature/bgp/otg_tests/bgp_tcp_mss_path_mtu/metadata.textproto @@ -24,6 +24,7 @@ platform_exceptions: { interface_enabled: true default_network_instance: "default" isis_interface_afi_unsupported: true + fr_breakout_fix: true } } platform_exceptions: { diff --git a/feature/bgp/otg_tests/multihop/metadata.textproto b/feature/bgp/otg_tests/multihop/metadata.textproto new file mode 100644 index 00000000000..e08775624d6 --- /dev/null +++ b/feature/bgp/otg_tests/multihop/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "23d3189f-65e2-4cd7-80b8-a0b890859607" +plan_id: "RT-1.63" +description: "BGP Multihop" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/bgp/policybase/feature.textproto b/feature/bgp/policybase/feature.textproto deleted file mode 100644 index 645d0930e23..00000000000 --- a/feature/bgp/policybase/feature.textproto +++ /dev/null @@ -1,356 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_policybase" - version: 1 -} - -#Configuration - -# Defined-sets leaves - -#community - -##community-member -config_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-member" -} -telemetry_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-member" -} -##community-set-name -config_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/community-set-name" -} -telemetry_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/community-set-name" -} -##options:all,any,invert. default=any -config_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/config/match-set-options" -} -telemetry_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/community-sets/community-set/state/match-set-options" -} - -#as-path -##as-path-member -config_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-member" -} -telemetry_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/state/as-path-set-member" -} -##as-path-set-name -config_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-name" -} -telemetry_path { - path: "/routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/state/as-path-set-name" -} - -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/name" -} -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/config/name" -} -telemetry_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/state/name" -} -##prefix-set-mode:ipv4,ipv6,mixed -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/config/mode" -} -telemetry_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/state/mode" -} -##prefixes -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/ip-prefix" -} -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/ip-prefix" -} - -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/masklength-range" -} -config_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/masklength-range" -} -telemetry_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/ip-prefix" -} -telemetry_path { - path: "/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/state/masklength-range" -} - -config_path { - path: "/routing-policy/policy-definitions/policy-definition/name" -} -config_path { - path: "/routing-policy/policy-definitions/policy-definition/config/name" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/state/name" -} - -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/name" -} -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/config/name" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/state/name" -} - -# Policy set/action leaves -##set-route-origin -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-route-origin" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-route-origin" -} -##set-local-pref -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-local-pref" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-local-pref" -} -##set-med -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-med" -} -##set-as-path-prepend(16|32bit) -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/repeat-n" -} -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/state/asn" -} -##set-nexthop-self -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-next-hop" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/state/set-next-hop" -} -##set-community -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/config/method" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/state/method" -} -###options:add,remove,replace -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/config/options" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/state/options" -} -###method:inline -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/inline/config/communities" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/inline/state/communities" -} -###method:reference -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/config/community-set-ref" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-community/reference/state/community-set-ref" -} - -# Policy condition/match leaves - -##match-next-hop -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/next-hop-in" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/next-hop-in" -} -##match-origin -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/origin-eq" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/origin-eq" -} -##match-med -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/med-eq" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/med-eq" -} -##match-local-pref -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/local-pref-eq" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/local-pref-eq" -} -##match-community-set -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/config/community-set" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/state/community-set" -} -##match-prefix-set -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/prefix-set" -} -###type:any,all,invert. default=any -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/state/match-set-options" -} - -##match-as-path-set -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/config/as-path-set" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/state/as-path-set" -} -###type:any,all,invert. default=any -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/config/match-set-options" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-as-path-set/state/match-set-options" -} -#policy-result -config_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result" -} -telemetry_path { - path: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/state/policy-result" -} - -# Per neighbor policy telemetry paths -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/afi-safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy" -} - - -# Per prefix-list policy telemetry paths -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/installed" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/state/export-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/state/import-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/config/default-import-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/config/default-export-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/default-export-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/default-import-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/state/default-import-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/state/default-export-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/state/default-export-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/state/default-import-policy" -} - -# Apply policy telemetry paths -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/config/export-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/config/import-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/state/export-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/apply-policy/state/import-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/state/export-policy" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/state/import-policy" -} -feature_profile_dependency { - name: "bgp" - version: 1 -} - - - diff --git a/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md b/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md index 82d6fabacce..0e1f1bf5d85 100644 --- a/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md +++ b/feature/bgp/policybase/otg_tests/3level_nested_policies/README.md @@ -427,47 +427,49 @@ For each section of configuration below, prepare a gnmi.SetBatch with all the c * Initiate traffic from ATE Port-1 towards the DUT destined ```ipv6-network-2``` i.e. ```2024:db8:64:64::/64``` * Validate that the traffic is received on ATE Port-2 - -## Config parameter coverage - -* /network-instances/network-instance/protocols/protocol/bgp/global/config -* /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/ -* /routing-policy/policy-definitions/policy-definition/config/name -* /routing-policy/policy-definitions/policy-definition/statements/statement/config/name -* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result -* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy -* /routing-policy/defined-sets/prefix-sets/prefix-set/config/name -* /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode -* /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/ip-prefix -* /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/masklength-range -* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options -* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set -* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med -* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn -* /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n -* /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy - -## Telemetry parameter coverage - -* /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy -* /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy -* /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member -* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix -* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix -* /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med -* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix -* /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/community-index - -## Protocol/RPC Parameter Coverage - -* gNMI - * Get - * Set +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config parameter coverage + /routing-policy/policy-definitions/policy-definition/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/config/name: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/config/call-policy: + /routing-policy/defined-sets/prefix-sets/prefix-set/config/name: + /routing-policy/defined-sets/prefix-sets/prefix-set/config/mode: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/ip-prefix: + /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/masklength-range: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/match-set-options: + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/asn: + /routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/set-as-path-prepend/config/repeat-n: + /network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy: + + + ## Telemetry parameter coverage + /routing-policy/policy-definitions/policy-definition/statements/statement/conditions/state/call-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/as-path/as-segment/state/member: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib/routes/route/prefix: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/prefix: + /network-instances/network-instance/protocols/protocol/bgp/rib/attr-sets/attr-set/state/med: + /network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/community-index: + + ## Protocol/RPC Parameter Coverage +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: +``` ## Required DUT platform diff --git a/feature/bgp/policybase/otg_tests/3level_nested_policies/metadata.textproto b/feature/bgp/policybase/otg_tests/3level_nested_policies/metadata.textproto new file mode 100644 index 00000000000..c9a7c47bbbb --- /dev/null +++ b/feature/bgp/policybase/otg_tests/3level_nested_policies/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "cf8a0f24-e373-4b55-af99-70371c958dc7" +plan_id: "RT-1.31" +description: "BGP 3 levels of nested import/export policy with match-set-options" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/bgp/policybase/otg_tests/Import_Export_Policy/README.md b/feature/bgp/policybase/otg_tests/Import_Export_Policy/README.md new file mode 100644 index 00000000000..44e6d600c0e --- /dev/null +++ b/feature/bgp/policybase/otg_tests/Import_Export_Policy/README.md @@ -0,0 +1,234 @@ +# RT-1.64: BGP Import/Export Policy (Control plane only) Functional Test Case + +## Objective + +To verify the basic functioanlity of BGP import and export policies and to check if they are correctly applied on a router. + +## Test Bed Setup + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +* Device Under Test (DUT): 1 x Router +* ATE BGP Peer: 1 x ATE with BGP Emulation capabilities. + +### Connections: +One Ethernet link between a port on the DUT and a port on the ATE. +Create the following connections: +```mermaid +graph LR; +subgraph ATE [ATE] + A1[Port1] +end +subgraph DUT1 [DUT1] + B1[Port1] +end +A1 <-- EBGP --> B1; +``` +DUT interface: IPV4 - 10.1.1.0/31 & IPV6 - 2001:db8:204:114::0/127 +ATE interface: IPV4 - 10.1.1.1/31 & IPV6 - 2001:db8:204:114::1/127 + +## Prerequisites + +* Basic IPv4 and IPv6 connectivity configured between the DUT and ATE interface. +* BGP peering established between the DUT and ATE on the connected interfaces for both IPV4 and IPV6 address families. +* Access to configure the DUT and ATE. +* BGP parameters to be defined that includes the RouterID, AS paths to be used. + +## Test Case Scenario + +This test case will focus on two scenarios: + +* Export Policy: Router exports only specific prefixes to ATE based on a policy. +* Import Policy: Router imports only specific prefixes from ATE based on a policy. + +## Test Steps + +### Initial Setup (Pre-Test) + +### DUT Configuration (Basic BGP Peering): + +* Configure interface IP addresses (DUT: IPV4 - 10.1.1.0/31 & IPV6 - 2001:db8:204:114::0/127 , ATE: IPV4 - 10.1.1.1/31 & IPV6 - 2001:db8:204:114::1/127 ). +* Enable BGP on the DUT. +* Establish an EBGP peering session with the ATE (using ATE's AS as AS65002). +* Ensure the DUT has some local routes (loopback interfaces advertised into BGP) that can be filtered. +* Routes advertised by DUT: + * IPV4: 172.16.1.0/24, 172.16.2.0/24, 192.168.10.0/24 + * IPV6: 2001:db8:250:110::/64, 2001:db8:251:110::/64, 2001:db8:299:110::/64 +* Routes advertised by ATE: + * IPV4: 192.0.2.1/32, 192.0.2.2/32, 198.51.100.1/32, 198.51.100.2/32 + * IPV6: 2001:db8:300:100::0/127, 2001:db8:300:101::0/127, 2001:db8:400:100::1/128, 2001:db8:400:101::1/128 + +### ATE Configuration (Basic BGP Emulation): + +* Configure the ATE port with an IP address (10.1.1.1/31) & IPv6 address (2001:db8:204:114::1/127). +* Create a BGP Emulated Router on the ATE, acting as AS 65002. +* Establish a BGP peering session with the DUT's IP (10.1.1.0) & IPV6 (2607:f8b0:8007:614f::). + +* Advertise multiple unique BGP routes from ATE, some intended to be filtered, and some to be allowed on the DUT. + * Allowed routes: IPV4 192.0.2.1/32 (AS-Path: 65002), 192.0.2.2/32 (AS-Path: 65002) and IPV6 2001:db8:300:100::0/127 (AS-Path: 65002) , 2001:db8:300:101::0/127 (AS-Path: 65002) + * Denied routes: IPV4 198.51.100.1/32 (AS-Path: 65002 65003), 198.51.100.2/32 (AS-Path: 65002 65004) and IPV6 2001:db8:400:100::1/128 (AS-Path: 65002 65004) , 2001:db8:400:101::1/128 (AS-Path: 65002 65004) + +### RT-1.64.1 Verify BGP Peering without policy + +* On DUT: + * Check the BGP summary and make sure the session towards ATE is established. + * Check if the routes advertised by ATE are received on the DUT, using OC afts state paths + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + * /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix + +* On ATE: + * Check BGP session status towards DUT and the session should be in established state. + * Check if the DUT advertised routes are present on the ATE + +### RT-1.64.2 Test Export Policy (Prefix-list based) + +Objective: Only allow local routes 172.16.1.0/24 and 172.16.2.0/24 & 2001:db8:250:110::/64 and 2001:db8:251:110::/64 to be advertised from DUT to ATE. 192.168.10.0/24 & 2001:db8:299:110::/64 should be denied. + +Configure Export Policy on DUT: +Create a routing policy. +Apply the routing policy to the BGP neighbor 10.1.1.1 & 2607:f8b0:8007:614f::1 as an output policy. + +### Verify Export Policy: + +* On DUT: + * Check the BGP summary and make sure the session towards ATE is established. + * Check if the routes advertised by ATE are received on the DUT, using OC afts state paths + * /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + * /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix + +* On ATE: + * Check BGP session status towards DUT and the session should be in established state. + * Check if the DUT advertised routes are present on the ATE + +### RT-1.64.3 Test Import Policy (AS-Path based) + +* Objective: Only allow routes from ATE with AS-Path 65002 (originating from the immediate neighbor) to be imported into the DUT's BGP table. Routes with longer AS-Paths (e.g., 65002 65003) should be denied. + +* Configure Import Policy on DUT: + * Create a routing policy to match the desired AS-Path and prefixes. + * Apply the rrouting policy to the BGP neighbor IPV4 - 10.1.1.1 & IPV6 - 2001:db8:204:114::1 as an import policy. + + +### Verify Import Policy: + +* DUT: + * Check the bgp neighbors and received routes from ATE in DUT BGP table + * Routes 192.0.2.1/32 (AS-Path: 65002), 192.0.2.2/32 (AS-Path: 65002), 2001:db8:300:100::0/127 (AS-Path: 65002), 2001:db8:300:101::0/127 (AS-Path: 65002) should be received. + * Routes 198.51.100.1/32, 198.51.100.2/32, 2001:db8:400:100::1/128, 2001:db8:400:101::1/128 should be rejected. + * Check the bgp neighbors 10.1.1.1 & 2001:db8:204:114::1 received-routes and all the routes from ATE should be shown as routing policy doesnt get applied yet. + +* ATE: + * Observe the "Advertised Routes" from the ATE to the DUT. + * Expected: All original advertised routes (192.0.2.1/32, 192.0.2.2/32, 198.51.100.1/32, 198.51.100.2/32, 2001:db8:300:100::0/127,2001:db8:300:101::0/127,2001:db8:400:100::1/128, 2001:db8:400:101::1/128 ) should still be showing as advertised by ATE. The filtering happens on the DUT. + +### Expected Results + +* Export Policy: The ATE should only learn the BGP prefixes 172.16.1.0/24 and 172.16.2.0/24 & 2001:db8:250:110::/64 and 2001:db8:251:110::/64 from the DUT. Other local prefixes should not be learned by ATE. +* Import Policy: The DUT's AFT table should only contain the BGP prefixes 192.0.2.1/32 and 192.0.2.2/32 & 2001:db8:300:100::0/127 and 2001:db8:300:101::0/127 learned from ATE. Prefixes with longer AS-Paths should be filtered and not appear in the DUT's AFT table. + + +## Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "apply-policy": { + "config": { + "export-policy": [ + "test_export" + ], + "import-policy": [ + "test_import" + ] + } + }, + "config": { + "neighbor-address": "192.168.1.1" + }, + "graceful-restart": { + "config": { + "enabled": true + } + }, + "neighbor-address": "192.168.1.1" + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + }, + "routing-policy": { + "policy-definitions": { + "policy-definition": [ + { + "config": { + "name": "test_export" + }, + "name": "test_export" + }, + { + "config": { + "name": "test_import" + }, + "name": "test_import" + } + ] + } + } +} +``` + + +#### OpenConfig Path and RPC Coverage + +```yaml +paths: + +#Telemetry + +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state: +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/neighbor-address: +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state: +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/peer-type: +/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/state/import-policy: +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/export-policy: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF +* MFF + + + diff --git a/feature/bgp/policybase/otg_tests/Import_Export_Policy/bgp_import_export_policy_test.go b/feature/bgp/policybase/otg_tests/Import_Export_Policy/bgp_import_export_policy_test.go new file mode 100644 index 00000000000..200234f9193 --- /dev/null +++ b/feature/bgp/policybase/otg_tests/Import_Export_Policy/bgp_import_export_policy_test.go @@ -0,0 +1,569 @@ +// 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 bgp_import_export_policy_test + +import ( + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "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" +) + +// The testbed consists of ate:port1 -> dut:port1. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// List of variables. +var ( + dutAttrs = attrs.Attributes{ + Desc: "DUT port towards OTG", + IPv4: "10.1.1.0", + IPv4Len: 31, + IPv6: "2001:db8:204:114::0", + IPv6Len: 127, + } + ateAttrs = attrs.Attributes{ + Desc: "OTG port towards DUT", + Name: "ateSrc", + MAC: "00:00:01:02:03:04", + IPv4: "10.1.1.1", + IPv4Len: 31, + IPv6: "2001:db8:204:114::1", + IPv6Len: 127, + } + dutloAttrs = attrs.Attributes{ + Desc: "DUT Loopback port for static route injection", + IPv4: "1.2.3.4", + IPv4Len: 24, + IPv6: "2001:db8::2:3:4:5", + IPv6Len: 64, + } + loopbackIntfName string + dutAdvertisedRoutesv4Net = []string{"172.16.1.0", "172.16.2.0", "192.168.10.0"} + dutAdvertisedRoutesv6Net = []string{"2001:db8:250:110::0", "2001:db8:251:110::0", "2001:db8:299:110::0"} + otgAdvertisedRoutesv4Net = []string{"192.0.2.1", "192.0.2.2", "198.51.100.1", "198.51.100.2"} + otgDeniedRoutesv4Net = []string{"198.51.100.1", "198.51.100.2"} + otgAdvertisedRoutesv6Net = []string{"2001:db8:300:100::0", "2001:db8:300:101::0", "2001:db8:400:100::1", "2001:db8:400:101::1"} + otgDeniedRoutesv6Net = []string{"2001:db8:400:100::1", "2001:db8:400:101::1"} + otgAdvertisedRoutesv4Prefix = []uint32{32, 32, 32, 32} + otgAdvertisedRoutesv6Prefix = []uint32{127, 127, 128, 128} + routeCount = 1 +) + +// Constants. +const ( + dutAS = 65001 + ateAS = 65002 + peerGrpName = "eBGP-PEER-GROUP" + peerLvlPassive = "PeerGrpLevelPassive" + peerLvlActive = "PeerGrpLevelActive" + nbrLvlPassive = "nbrLevelPassive" + nbrLvlActive = "nbrLevelActive" + rplPermitAll = "PERMIT-ALL" + rejectAspath = "REJECT-AS-PATH" + regexAsSet = "REGEX-AS-SET" +) + +// configureDUT is used to configure interfaces on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dc := gnmi.OC() + i1 := dutAttrs.NewOCInterface(dut.Port(t, "port1").Name(), dut) + gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dut.Port(t, "port1")) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, i1.GetName(), deviations.DefaultNetworkInstance(dut), 0) + } + + // Configure loopback addresses to be able to advertise dutAdvertisedRoutesv4Net towards OTG + + for i := range dutAdvertisedRoutesv4Net { + loopbackIntfName = netutil.LoopbackInterface(t, dut, i) + loIntf := gnmi.OC().Interface(loopbackIntfName).Subinterface(uint32(i)) + ipv4Addrs := gnmi.LookupAll(t, dut, loIntf.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, loIntf.Ipv6().AddressAny().State()) + if len(ipv4Addrs) == 0 && len(ipv6Addrs) == 0 { + hostsv4 := hostify(dutAdvertisedRoutesv4Net) + hostsv6 := hostify(dutAdvertisedRoutesv6Net) + dutloAttrs.IPv4 = hostsv4[i] + dutloAttrs.IPv6 = hostsv6[i] + loop1 := dutloAttrs.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 { + dutloAttrs.IPv4 = v4.GetIp() + } + v6, ok := ipv6Addrs[0].Val() + if ok { + dutloAttrs.IPv6 = v6.GetIp() + } + t.Logf("Got DUT IPv4 loopback address: %v", dutloAttrs.IPv4) + t.Logf("Got DUT IPv6 loopback address: %v", dutloAttrs.IPv6) + } + } + +} + +func hostify(ipList []string) []string { + var result []string + + for _, ip := range ipList { + if strings.Contains(ip, ".") { + parts := strings.Split(ip, ".") + parts[len(parts)-1] = "1" + result = append(result, strings.Join(parts, ".")) + } else if strings.Contains(ip, ":") { + parts := strings.Split(ip, ":") + parts[len(parts)-1] = "1" + result = append(result, strings.Join(parts, ":")) + } else { + // No delimiter found, skip or return as-is + result = append(result, ip) + } + } + return result +} + +// verifyPortsUp asserts that each port on the device is operating. +func verifyPortsUp(t *testing.T, dev *ondatra.Device) { + t.Helper() + for _, p := range dev.Ports() { + status := gnmi.Get(t, dev, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_UP; status != want { + t.Errorf("%s Status: got %v, want %v", p, status, want) + } + } +} + +func contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + +// Struct is to pass bgp session parameters. +type bgpTestParams struct { + localAS, peerAS, nbrLocalAS uint32 + peerIP string + transportMode string + wantOTGPrefixesv4 []string + wantOTGPrefixesv6 []string + wantOTGDeniedPrefixesv4 []string + wantOTGDeniedPrefixesv6 []string +} + +// bgpCreateNbr creates a BGP object with neighbors pointing to ate and returns bgp object. +func bgpCreateNbr(t *testing.T, bgpParams *bgpTestParams, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + t.Helper() + d := &oc.Root{} + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.As = ygot.Uint32(bgpParams.localAS) + global.RouterId = ygot.String(dutAttrs.IPv4) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + + // Note: we have to define the peer group even if we aren't setting any policy because it's + // invalid OC for the neighbor to be part of a peer group that doesn't exist. + pg := bgp.GetOrCreatePeerGroup(peerGrpName) + pg.PeerAs = ygot.Uint32(ateAS) + pg.PeerGroupName = ygot.String(peerGrpName) + pgT := pg.GetOrCreateTransport() + pgT.LocalAddress = ygot.String(dutAttrs.IPv4) + pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + + nv4 := bgp.GetOrCreateNeighbor(ateAttrs.IPv4) + nv4.PeerGroup = ygot.String(peerGrpName) + nv4.PeerAs = ygot.Uint32(ateAS) + nv4.Enabled = ygot.Bool(true) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + nv4T := nv4.GetOrCreateTransport() + nv4T.LocalAddress = ygot.String(dutAttrs.IPv4) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + + switch bgpParams.transportMode { + case nbrLvlPassive: + nv4.GetOrCreateTransport().SetPassiveMode(true) + case nbrLvlActive: + nv4.GetOrCreateTransport().SetPassiveMode(false) + case peerLvlPassive: + pg.GetOrCreateTransport().SetPassiveMode(true) + case peerLvlActive: + pg.GetOrCreateTransport().SetPassiveMode(false) + } + + return niProto +} + +// bgpClearConfig removes all BGP configuration from the DUT. +func bgpClearConfig(t *testing.T, dut *ondatra.DUTDevice) { + resetBatch := &gnmi.SetBatch{} + gnmi.BatchDelete(resetBatch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config()) + + if deviations.NetworkInstanceTableDeletionRequired(dut) { + tablePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableAny() + for _, table := range gnmi.LookupAll[*oc.NetworkInstance_Table](t, dut, tablePath.Config()) { + if val, ok := table.Val(); ok { + if val.GetProtocol() == oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP { + gnmi.BatchDelete(resetBatch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Table(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, val.GetAddressFamily()).Config()) + } + } + } + } + resetBatch.Set(t, dut) +} + +// verifyBgpTelemetry checks that the dut has an established BGP session with reasonable settings. +func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice, wantState oc.E_Bgp_Neighbor_SessionState, transMode string, transModeOnATE string) { + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + nbrPath := statePath.Neighbor(ateAttrs.IPv4) + if deviations.BgpSessionStateIdleInPassiveMode(dut) { + if transModeOnATE == nbrLvlPassive || transModeOnATE == peerLvlPassive { + t.Logf("BGP session state idle is supported in passive mode, transMode: %s, transModeOnATE: %s", transMode, transModeOnATE) + wantState = oc.Bgp_Neighbor_SessionState_IDLE + } + } + // Get BGP adjacency state + t.Log("Checking BGP neighbor to state...") + _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, present := val.Val() + return present && state == wantState + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + t.Errorf("BGP Session state is not as expected.") + } + status := gnmi.Get(t, dut, nbrPath.SessionState().State()) + t.Logf("BGP adjacency for %s: %s", ateAttrs.IPv4, status) + t.Logf("wantState: %s, status: %s", wantState, status) + if status != wantState { + t.Errorf("BGP peer %s status got %d, want %d", ateAttrs.IPv4, status, wantState) + } + + nbrTransMode := gnmi.Get(t, dut, nbrPath.Transport().State()) + pgTransMode := gnmi.Get(t, dut, statePath.PeerGroup(peerGrpName).Transport().State()) + t.Logf("Neighbor level passive mode is set to %v on DUT", nbrTransMode.GetPassiveMode()) + t.Logf("Peer group level passive mode is set to %v on DUT", pgTransMode.GetPassiveMode()) + + // Check transport mode telemetry. + switch transMode { + case nbrLvlPassive: + if nbrTransMode.GetPassiveMode() != true { + t.Errorf("Neighbor level passive mode is not set to true on DUT. want true, got %v", nbrTransMode.GetPassiveMode()) + } + t.Logf("Neighbor level passive mode is set to %v on DUT", nbrTransMode.GetPassiveMode()) + case nbrLvlActive: + if nbrTransMode.GetPassiveMode() != false { + t.Errorf("Neighbor level passive mode is not set to false on DUT. want false, got %v", nbrTransMode.GetPassiveMode()) + } + t.Logf("Neighbor level passive mode is set to %v on DUT", nbrTransMode.GetPassiveMode()) + case peerLvlPassive: + if pgTransMode.GetPassiveMode() != true { + t.Errorf("Peer group level passive mode is not set to true on DUT. want true, got %v", pgTransMode.GetPassiveMode()) + } + t.Logf("Peer group level passive mode is set to %v on DUT", pgTransMode.GetPassiveMode()) + case peerLvlActive: + if pgTransMode.GetPassiveMode() != false { + t.Errorf("Peer group level passive mode is not set to false on DUT. want false, got %v", pgTransMode.GetPassiveMode()) + } + t.Logf("Peer group level passive mode is set to %v on DUT", pgTransMode.GetPassiveMode()) + } +} + +// Function to configure ATE configs based on args and returns ate topology handle. +func configureATE(t *testing.T, ateParams *bgpTestParams, optionalAsPath []uint32, optionalAsPathRouteTarget []string) gosnappi.Config { + t.Helper() + ate := ondatra.ATE(t, "ate") + port1 := ate.Port(t, "port1") + topo := gosnappi.NewConfig() + + topo.Ports().Add().SetName(port1.ID()) + dev := topo.Devices().Add().SetName(ateAttrs.Name) + eth := dev.Ethernets().Add().SetName(ateAttrs.Name + ".Eth") + eth.Connection().SetPortName(port1.ID()) + eth.SetMac(ateAttrs.MAC) + + ip := eth.Ipv4Addresses().Add().SetName(dev.Name() + ".IPv4") + ip.SetAddress(ateAttrs.IPv4).SetGateway(dutAttrs.IPv4).SetPrefix(uint32(ateAttrs.IPv4Len)) + + ip6 := eth.Ipv6Addresses().Add().SetName(dev.Name() + ".IPv6") + ip6.SetAddress(ateAttrs.IPv6).SetGateway(dutAttrs.IPv6).SetPrefix(uint32(ateAttrs.IPv6Len)) + + // Configure BGP peers + bgp4 := dev.Bgp().SetRouterId(ateAttrs.IPv4) + peerBGP4 := bgp4.Ipv4Interfaces().Add().SetIpv4Name(ip.Name()).Peers().Add() + peerBGP4.SetName(ateAttrs.Name + ".BGP4.peer") + peerBGP4.SetPeerAddress(ip.Gateway()).SetAsNumber(uint32(ateParams.localAS)) + peerBGP4.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + //bgp6 := dev.Bgp().SetRouterId(ateAttrs.IPv6) + peerBGP6 := bgp4.Ipv6Interfaces().Add().SetIpv6Name(ip6.Name()).Peers().Add() + peerBGP6.SetName(ateAttrs.Name + ".BGP6.peer") + peerBGP6.SetPeerAddress(ip6.Gateway()).SetAsNumber(uint32(ateParams.localAS)) + peerBGP6.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + switch ateParams.transportMode { + case nbrLvlPassive: + peerBGP4.Advanced().SetPassiveMode(true) + case peerLvlPassive: + peerBGP4.Advanced().SetPassiveMode(true) + case peerLvlActive: + peerBGP4.Advanced().SetPassiveMode(false) + case nbrLvlActive: + peerBGP4.Advanced().SetPassiveMode(false) + } + + // Configure BGP routes to be pushed to DUT + bgp4PeerRoutes := peerBGP4.V4Routes().Add().SetName(ateAttrs.Name + ".BGP4.peer" + ".RR4") + bgp4PeerRoutes.SetNextHopIpv4Address(ip.Address()).SetNextHopAddressType(gosnappi.BgpV4RouteRangeNextHopAddressType.IPV4).SetNextHopMode(gosnappi.BgpV4RouteRangeNextHopMode.MANUAL) + for i, rr := range ateParams.wantOTGPrefixesv4 { + bgp4PeerRoutes.Addresses().Add().SetAddress(rr).SetPrefix(otgAdvertisedRoutesv4Prefix[i]).SetCount(uint32(routeCount)) + if len(optionalAsPath) > 0 && len(optionalAsPathRouteTarget) > 0 && contains(optionalAsPathRouteTarget, rr) { + asp4 := bgp4PeerRoutes.AsPath().Segments().Add() + asp4.SetAsNumbers(optionalAsPath) + } + + } + + bgp6PeerRoutes := peerBGP6.V6Routes().Add().SetName(ateAttrs.Name + ".BGP6.peer" + ".RR6") + bgp6PeerRoutes.SetNextHopIpv6Address(ip6.Address()).SetNextHopAddressType(gosnappi.BgpV6RouteRangeNextHopAddressType.IPV6).SetNextHopMode(gosnappi.BgpV6RouteRangeNextHopMode.MANUAL) + for i, rr := range ateParams.wantOTGPrefixesv6 { + bgp6PeerRoutes.Addresses().Add().SetAddress(rr).SetPrefix(otgAdvertisedRoutesv6Prefix[i]).SetCount(uint32(routeCount)) + } + + return topo +} + +func verifyOTGBGPTelemetry(t *testing.T, otg *otg.OTG, c gosnappi.Config) { + // nbrPath := gnmi.OTG().BgpPeer("ateSrc.BGP4.peer") + t.Log("OTG telemetry does not support checking transport mode.") +} + +func verifyPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, wantInstalled uint32, isV4 bool) { + t.Helper() + t.Logf("Verify BGP prefix count") + if isV4 { + verifyPrefixesTelemetryV4(t, dut, wantInstalled) + } else { + verifyPrefixesTelemetryV6(t, dut, wantInstalled) + } +} + +// verifyPrefixesTelemetry confirms that the dut shows the correct numbers of installed, sent and +// received IPv4 prefixes +func verifyPrefixesTelemetryV4(t *testing.T, dut *ondatra.DUTDevice, wantInstalled uint32) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + prefixesv4 := statePath.Neighbor(ateAttrs.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes() + + if gotInstalled := gnmi.Get(t, dut, prefixesv4.Installed().State()); gotInstalled != wantInstalled { + t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, wantInstalled) + } +} + +// verifyPrefixesTelemetryV6 confirms that the dut shows the correct numbers of installed, sent and +// received IPv6 prefixes +func verifyPrefixesTelemetryV6(t *testing.T, dut *ondatra.DUTDevice, wantInstalledv6 uint32) { + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + prefixesv6 := statePath.Neighbor(ateAttrs.IPv6).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Prefixes() + + if gotInstalledv6 := gnmi.Get(t, dut, prefixesv6.Installed().State()); gotInstalledv6 != wantInstalledv6 { + t.Errorf("IPV6 Installed prefixes mismatch: got %v, want %v", gotInstalledv6, wantInstalledv6) + } +} + +func configurePrefixMatchPolicy(t *testing.T, dut *ondatra.DUTDevice, prefixSet, prefixSubnetRange, maskLen string, ipPrefixSet []string) *oc.RoutingPolicy { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pset := rp.GetOrCreateDefinedSets().GetOrCreatePrefixSet(prefixSet) + for _, pref := range ipPrefixSet { + pset.GetOrCreatePrefix(pref+"/"+maskLen, prefixSubnetRange) + mode := oc.PrefixSet_Mode_IPV4 + if maskLen == "128" { + mode = oc.PrefixSet_Mode_IPV6 + } + if !deviations.SkipPrefixSetMode(dut) { + pset.SetMode(mode) + } + } + + pdef := rp.GetOrCreatePolicyDefinition(prefixSet) + stmt5, err := pdef.AppendNewStatement("10") + if err != nil { + t.Logf("Statement definition: %v", err) + } + stmt5.GetOrCreateConditions().GetOrCreateMatchPrefixSet().PrefixSet = ygot.String(prefixSet) + stmt5.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + return rp +} + +func configureRoutePolicies(t *testing.T, dut *ondatra.DUTDevice, policyType string) { + t.Helper() + if policyType == "allow-all" { + } + if policyType == "pfx-based-allow" { + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + batchConfig := &gnmi.SetBatch{} + + gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchPolicy(t, dut, "EBGP-IMPORT-IPV4", "exact", "32", []string{otgAdvertisedRoutesv4Net[0], otgAdvertisedRoutesv4Net[1]})) + + // Apply the above policies to the respective peering at the repective AFI-SAFI levels + gnmi.BatchReplace(batchConfig, bgpPath.Neighbor(ateAttrs.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy().ImportPolicy().Config(), []string{"EBGP-IMPORT-IPV4"}) + + batchConfig.Set(t, dut) + } + if policyType == "as-path-deny" { + if deviations.BgpAspathsetUnsupported(dut) { + if dut.Vendor() == ondatra.ARISTA { + cfgplugins.DeviationAristaRoutingPolicyBGPAsPathSetUnsupported(t, dut, "aclRegexAsPathAllowed", "FILTER-IN", "^65002$") + } + } else { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateAsPathSet(regexAsSet).SetAsPathSetMember([]string{"65002 .*"}) + pdefAs := rp.GetOrCreatePolicyDefinition(rejectAspath) + + stmt70, err := pdefAs.AppendNewStatement("20") + if err != nil { + t.Errorf("Error while creating new statement %v", err) + } + stmt70.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_REJECT_ROUTE + stmt70.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetAsPathSet(regexAsSet) + stmt70.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchAsPathSet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) + + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + } + + } +} + +func TestBgpImportExportPolicy(t *testing.T) { + t.Logf(" **************") + t.Logf(" *** RT1.64 ***") + t.Logf(" **************") + dutIP := dutAttrs.IPv4 + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + // Configure interface on the DUT + t.Log("Start DUT interface Config") + configureDUT(t, dut) + + // Configure Network instance type on DUT + t.Log("Configure Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + // Verify Port Status + t.Log("Verifying port status") + verifyPortsUp(t, dut.Device) + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + cases := []struct { + name string + dutConf *oc.NetworkInstance_Protocol + ateConf gosnappi.Config + wantBGPState oc.E_Bgp_Neighbor_SessionState + dutTransportMode string + otgTransportMode string + expectedPrefixes uint32 + policyType string + }{ + { + name: "### RT-1.64.1 Verify BGP Peering without policy", + dutConf: bgpCreateNbr(t, &bgpTestParams{localAS: dutAS, peerAS: ateAS, nbrLocalAS: ateAS, transportMode: nbrLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: nbrLvlActive, wantOTGPrefixesv4: otgAdvertisedRoutesv4Net, wantOTGPrefixesv6: otgAdvertisedRoutesv6Net, wantOTGDeniedPrefixesv4: otgDeniedRoutesv4Net, wantOTGDeniedPrefixesv6: otgDeniedRoutesv6Net}, []uint32{}, []string{}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ESTABLISHED, + dutTransportMode: nbrLvlPassive, + otgTransportMode: nbrLvlActive, + expectedPrefixes: 4, + policyType: "allow-all", + }, + { + name: "### RT-1.64.2 Test Export Policy (Prefix-list based)", + dutConf: bgpCreateNbr(t, &bgpTestParams{localAS: dutAS, peerAS: ateAS, nbrLocalAS: ateAS, transportMode: nbrLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: nbrLvlActive, wantOTGPrefixesv4: otgAdvertisedRoutesv4Net, wantOTGPrefixesv6: otgAdvertisedRoutesv6Net, wantOTGDeniedPrefixesv4: otgDeniedRoutesv4Net, wantOTGDeniedPrefixesv6: otgDeniedRoutesv6Net}, []uint32{}, []string{}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ESTABLISHED, + dutTransportMode: nbrLvlPassive, + otgTransportMode: nbrLvlActive, + expectedPrefixes: 2, + policyType: "pfx-based-allow", + }, + { + name: "### RT-1.64.3 Test Import Policy (AS-Path based)", + dutConf: bgpCreateNbr(t, &bgpTestParams{localAS: dutAS, peerAS: ateAS, nbrLocalAS: ateAS, transportMode: nbrLvlPassive}, dut), + ateConf: configureATE(t, &bgpTestParams{localAS: ateAS, peerIP: dutIP, transportMode: nbrLvlActive, wantOTGPrefixesv4: otgAdvertisedRoutesv4Net, wantOTGPrefixesv6: otgAdvertisedRoutesv6Net, wantOTGDeniedPrefixesv4: otgDeniedRoutesv4Net, wantOTGDeniedPrefixesv6: otgDeniedRoutesv6Net}, []uint32{}, []string{}), + wantBGPState: oc.Bgp_Neighbor_SessionState_ESTABLISHED, + dutTransportMode: nbrLvlPassive, + otgTransportMode: nbrLvlActive, + expectedPrefixes: 4, + policyType: "as-path-deny", + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + + t.Logf("Clear BGP configuration") + bgpClearConfig(t, dut) + + t.Logf("Configure BGP Configs on DUT") + gnmi.Replace(t, dut, dutConfPath.Config(), tc.dutConf) + fptest.LogQuery(t, "DUT BGP Config ", dutConfPath.Config(), gnmi.Get(t, dut, dutConfPath.Config())) + + t.Logf("Configuring route policies") + configureRoutePolicies(t, dut, tc.policyType) + + t.Log("Configure BGP on OTG") + ate.OTG().PushConfig(t, tc.ateConf) + ate.OTG().StartProtocols(t) + + t.Logf("Verify BGP telemetry") + verifyBgpTelemetry(t, dut, tc.wantBGPState, tc.dutTransportMode, tc.otgTransportMode) + + t.Logf("Verify BGP telemetry on OTG") + verifyOTGBGPTelemetry(t, ate.OTG(), tc.ateConf) + + t.Logf("Verify prefixes telemetry on DUT") + verifyPrefixesTelemetry(t, dut, tc.expectedPrefixes, true) + + t.Log("Clear BGP Configs on ATE") + ate.OTG().StopProtocols(t) + + }) + } +} diff --git a/feature/bgp/policybase/otg_tests/Import_Export_Policy/metadata.textproto b/feature/bgp/policybase/otg_tests/Import_Export_Policy/metadata.textproto new file mode 100644 index 00000000000..17fdbdc7d0d --- /dev/null +++ b/feature/bgp/policybase/otg_tests/Import_Export_Policy/metadata.textproto @@ -0,0 +1,35 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "bcde443f-b12a-49d1-80ce-7ca4d8f1de5b" +plan_id: "RT-1.64" +description: "BGP Import/Export Policy (Control plane only) Functional Test Case" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + bgp_aspathset_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + interface_enabled: true + } +} diff --git a/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/actions_MED_LocPref_prepend_flow_control_test.go b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/actions_MED_LocPref_prepend_flow_control_test.go index f9c72d658dd..0a8869e4509 100644 --- a/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/actions_MED_LocPref_prepend_flow_control_test.go +++ b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/actions_MED_LocPref_prepend_flow_control_test.go @@ -261,7 +261,9 @@ func configureASLocalPrefMEDPolicy(t *testing.T, dut *ondatra.DUTDevice, policyT } else { metric, _ := strconv.Atoi(policyValue) actions.GetOrCreateBgpActions().SetMed = oc.UnionUint32(uint32(metric)) - actions.GetOrCreateBgpActions().SetMedAction = oc.BgpPolicy_BgpSetMedAction_ADD + if !deviations.BGPSetMedActionUnsupported(dut) { + actions.GetOrCreateBgpActions().SetMedAction = oc.BgpPolicy_BgpSetMedAction_ADD + } actions.PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE } } else { diff --git a/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto index ae58a6b21fc..e87fbc3cdbf 100644 --- a/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/actions_med_localpref_prepend_flow_control/metadata.textproto @@ -26,6 +26,7 @@ platform_exceptions: { deviations: { bgp_rib_oc_path_unsupported: true bgp_set_med_v7_unsupported: true + bgp_set_med_action_unsupported: true } } platform_exceptions: { diff --git a/feature/bgp/policybase/otg_tests/chained_policies_test/chained_policies_test.go b/feature/bgp/policybase/otg_tests/chained_policies_test/chained_policies_test.go index 3a11c18a44d..65b93eaac57 100644 --- a/feature/bgp/policybase/otg_tests/chained_policies_test/chained_policies_test.go +++ b/feature/bgp/policybase/otg_tests/chained_policies_test/chained_policies_test.go @@ -37,16 +37,16 @@ import ( const ( ipv4PrefixLen = 30 ipv6PrefixLen = 126 - v41Route = "203.0.113.0" + v41Route = "203.0.113.1" v41TrafficStart = "203.0.113.1" - v42Route = "198.51.100.0" + v42Route = "198.51.100.1" v42TrafficStart = "198.51.100.1" - v4RoutePrefix = uint32(24) - v61Route = "2001:db8:128:128::" + v4RoutePrefix = uint32(32) + v61Route = "2001:db8:128:128::1" v61TrafficStart = "2001:db8:128:128::1" - v62Route = "2001:db8:128:129::" + v62Route = "2001:db8:128:129::1" v62TrafficStart = "2001:db8:128:129::1" - v6RoutePrefix = uint32(64) + v6RoutePrefix = uint32(128) dutAS = uint32(65656) ateAS1 = uint32(65657) ateAS2 = uint32(65658) @@ -226,9 +226,13 @@ func TestBGPChainedPolicies(t *testing.T) { if tc.ipv4 { createFlow(t, td, tc.flowConfig) + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) checkTraffic(t, td, v4Flow) } else { createFlowV6(t, td, tc.flowConfig) + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) checkTraffic(t, td, v6Flow) } }) diff --git a/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto b/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto index ac6627ae2f5..5f816a2700c 100644 --- a/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/chained_policies_test/metadata.textproto @@ -16,7 +16,6 @@ platform_exceptions: { missing_value_for_defaults: true skip_setting_statement_for_policy: true default_import_export_policy_unsupported: true - bgp_set_med_action_unsupported: true } } platform_exceptions: { @@ -25,6 +24,7 @@ platform_exceptions: { } deviations: { bgp_rib_oc_path_unsupported: true + bgp_set_med_action_unsupported: true } } platform_exceptions: { @@ -37,6 +37,7 @@ platform_exceptions: { skip_setting_statement_for_policy: true skip_checking_attribute_index: true flatten_policy_with_multiple_statements: true + bgp_set_med_action_unsupported: true } } platform_exceptions: { diff --git a/feature/bgp/policybase/otg_tests/comm_match_action_test/README.md b/feature/bgp/policybase/otg_tests/comm_match_action_test/README.md index 0f3f756d0d6..9da992066f6 100644 --- a/feature/bgp/policybase/otg_tests/comm_match_action_test/README.md +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/README.md @@ -12,6 +12,207 @@ criteria. * https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed +## Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "default" + }, + "name": "default", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "EXPORT-POLICY" + ], + "import-policy": [ + "IMPORT-POLICY" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST" + } + } + ] + }, + "config": { + "neighbor-address": "127.0.0.1" + }, + "neighbor-address": "127.0.0.1" + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "apply-policy": { + "config": { + "export-policy": [ + "EXPORT-POLICY" + ], + "import-policy": [ + "IMPORT-POLICY" + ] + } + }, + "config": { + "peer-group-name": "DEFAULT" + }, + "peer-group-name": "DEFAULT" + } + ] + }, + "rib": { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "ipv4-unicast": { + "neighbors": { + "neighbor": [ + { + "adj-rib-in-post": { + "routes": { + "route": [ + { + "path-id": 0, + "prefix": "0.0.0.0/0" + } + ] + } + }, + "neighbor-address": "127.0.0.1" + } + ] + } + } + }, + { + "afi-safi-name": "IPV6_UNICAST", + "ipv6-unicast": { + "neighbors": { + "neighbor": [ + { + "adj-rib-in-post": { + "routes": { + "route": [ + { + "path-id": 0, + "prefix": "::/0" + } + ] + } + }, + "neighbor-address": "127.0.0.1" + } + ] + } + } + } + ] + } + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + }, + "routing-policy": { + "defined-sets": { + "bgp-defined-sets": { + "community-sets": { + "community-set": [ + { + "community-set-name": "CS1", + "config": { + "community-member": [ + "1:1" + ], + "community-set-name": "CS1" + } + } + ] + } + } + }, + "policy-definitions": { + "policy-definition": [ + { + "config": { + "name": "EXPORT-POLICY" + }, + "name": "EXPORT-POLICY" + }, + { + "config": { + "name": "IMPORT-POLICY" + }, + "name": "IMPORT-POLICY" + }, + { + "config": { + "name": "POLICY1" + }, + "name": "POLICY1", + "statements": { + "statement": [ + { + "actions": { + "bgp-actions": { + "set-community": { + "reference": { + "config": { + "community-set-ref": "CS1" + } + } + } + } + }, + "conditions": { + "bgp-conditions": { + "match-community-set": { + "config": { + "match-set-options": "ANY" + } + } + } + }, + "config": { + "name": "10" + }, + "name": "10" + } + ] + } + } + ] + } + } +} +``` + ## Procedure * Testbed configuration - Setup eBGP sessions and prefixes. diff --git a/feature/bgp/policybase/otg_tests/comm_match_action_test/bgp_comm_match_action_test.go b/feature/bgp/policybase/otg_tests/comm_match_action_test/bgp_comm_match_action_test.go index 1ea239b2cb6..20c598d640a 100644 --- a/feature/bgp/policybase/otg_tests/comm_match_action_test/bgp_comm_match_action_test.go +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/bgp_comm_match_action_test.go @@ -200,6 +200,7 @@ func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkIns global := bgp.GetOrCreateGlobal() global.RouterId = ygot.String(dutPort1.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) @@ -209,17 +210,22 @@ func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkIns pg.PeerAs = ygot.Uint32(nbr.as) pg.PeerGroupName = ygot.String(nbr.peerGrp) - if deviations.SkipBgpSendCommunityType(dut) { - pg.SetSendCommunity(oc.E_Bgp_CommunityType(oc.Bgp_CommunityType_STANDARD)) - } else { - pg.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) - } - as4 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) as4.Enabled = ygot.Bool(true) as6 := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) as6.Enabled = ygot.Bool(true) + if deviations.SkipBgpSendCommunityType(dut) { + pg.SetSendCommunity(oc.E_Bgp_CommunityType(oc.Bgp_CommunityType_STANDARD)) + } else { + if deviations.SkipBgpPeerGroupSendCommunityType(dut) { + as4.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) + as6.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) + } else { + pg.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) + } + } + bgpNbr := bgp.GetOrCreateNeighbor(nbr.nbrAddr) bgpNbr.PeerGroup = ygot.String(nbr.peerGrp) bgpNbr.PeerAs = ygot.Uint32(nbr.as) diff --git a/feature/bgp/policybase/otg_tests/comm_match_action_test/metadata.textproto b/feature/bgp/policybase/otg_tests/comm_match_action_test/metadata.textproto index c3db3a287a3..7d7e90a7246 100644 --- a/feature/bgp/policybase/otg_tests/comm_match_action_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/comm_match_action_test/metadata.textproto @@ -13,7 +13,7 @@ platform_exceptions: { explicit_interface_in_default_vrf: true interface_enabled: true bgp_actions_set_community_method_unsupported: true - skip_bgp_send_community_type: true + skip_bgp_peer_group_send_community_type: true } } platform_exceptions: { diff --git a/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go b/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go index aee53cbf288..bcf37079dd8 100644 --- a/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go +++ b/feature/bgp/policybase/otg_tests/default_policies_test/bgp_default_policies_test.go @@ -467,7 +467,7 @@ func verifyInitialPrefixesTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr st } if !deviations.MissingPrePolicyReceivedRoutes(dut) { - if gotRx, ok := gnmi.Watch(t, dut, prefixPath.ReceivedPrePolicy().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + if gotRx, ok := gnmi.Watch(t, dut, prefixPath.ReceivedPrePolicy().State(), 30*time.Second, func(val *ygnmi.Value[uint32]) bool { gotRx, ok := val.Val() return ok && gotRx == wantRx }).Await(t); !ok { @@ -626,7 +626,7 @@ func configureRoutingPolicyDefaultAction(t *testing.T, dut *ondatra.DUTDevice, a deleteBGPPolicy(t, dut, []*bgpNbrList{ebgpNbrV4, ebgpNbrV6, ibgpNbrV4, ibgpNbrV6}) gnmi.BatchDelete(batchConfig, gnmi.OC().RoutingPolicy().Config()) batchConfig.Set(t, dut) - time.Sleep(20 * time.Second) + time.Sleep(40 * time.Second) gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ebgpImportIPv4, maskLenExact, maskLen32, []string{ipv4Prefix1, ipv4Prefix2}, action, defaultStatementOnly)) gnmi.BatchUpdate(batchConfig, gnmi.OC().RoutingPolicy().Config(), configurePrefixMatchAndDefaultStatement(t, dut, ebgpImportIPv6, maskLenExact, maskLen128, []string{ipv6Prefix1, ipv6Prefix2}, action, defaultStatementOnly)) @@ -649,7 +649,7 @@ func configureRoutingPolicyDefaultAction(t *testing.T, dut *ondatra.DUTDevice, a batchConfig.Set(t, dut) - time.Sleep(20 * time.Second) + time.Sleep(40 * time.Second) } func testDefaultPolicyRejectRouteAction(t *testing.T, dut *ondatra.DUTDevice) { @@ -837,7 +837,7 @@ func verifyPostPolicyPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr * } } - if gotInstalled, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Installed().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + if gotInstalled, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Installed().State(), 60*time.Second, func(val *ygnmi.Value[uint32]) bool { gotInstalled, ok := val.Val() return ok && gotInstalled == nbr.wantInstalled }).Await(t); !ok { @@ -845,14 +845,14 @@ func verifyPostPolicyPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr * } if !deviations.MissingPrePolicyReceivedRoutes(dut) { - if gotRxPrePol, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().ReceivedPrePolicy().State(), 20*time.Second, func(val *ygnmi.Value[uint32]) bool { + if gotRxPrePol, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().ReceivedPrePolicy().State(), 30*time.Second, func(val *ygnmi.Value[uint32]) bool { gotRxPrePol, ok := val.Val() return ok && gotRxPrePol == nbr.wantRxPrePolicy }).Await(t); !ok { t.Errorf("Received pre policy prefixes mismatch: got %v, want %v", gotRxPrePol, nbr.wantRxPrePolicy) } } - if gotRx, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Received().State(), 20*time.Second, func(val *ygnmi.Value[uint32]) bool { + if gotRx, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Received().State(), 30*time.Second, func(val *ygnmi.Value[uint32]) bool { gotRx, ok := val.Val() return ok && gotRx == nbr.wantRx }).Await(t); !ok { @@ -860,7 +860,7 @@ func verifyPostPolicyPrefixTelemetry(t *testing.T, dut *ondatra.DUTDevice, nbr * } if nbr.defImportPol == oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE && !deviations.SkipNonBgpRouteExportCheck(dut) { - if gotSent, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Sent().State(), 10*time.Second, func(val *ygnmi.Value[uint32]) bool { + if gotSent, ok := gnmi.Watch(t, dut, afiSafiPath.Prefixes().Sent().State(), 30*time.Second, func(val *ygnmi.Value[uint32]) bool { gotSent, ok := val.Val() return ok && gotSent == nbr.wantSent }).Await(t); !ok { diff --git a/feature/bgp/policybase/otg_tests/import_export_multi_test/metadata.textproto b/feature/bgp/policybase/otg_tests/import_export_multi_test/metadata.textproto index 9a3f61ab8a6..dc75d89aed1 100644 --- a/feature/bgp/policybase/otg_tests/import_export_multi_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/import_export_multi_test/metadata.textproto @@ -53,5 +53,6 @@ platform_exceptions: { deviations: { bgp_rib_oc_path_unsupported: true bgp_aspathset_unsupported: true + bgp_set_med_action_unsupported: true } } \ No newline at end of file diff --git a/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md b/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md index c725c5e4c7d..82891e05822 100644 --- a/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md +++ b/feature/bgp/policybase/otg_tests/link_bandwidth_test/README.md @@ -172,6 +172,12 @@ rpcs: gnmi: gNMI.Subscribe: ``` + +## Canonical OC +```json +{} +``` + ## Minimum DUT Required vRX - Virtual Router Device diff --git a/feature/bgp/policybase/otg_tests/link_bandwidth_test/link_bandwidth_test.go b/feature/bgp/policybase/otg_tests/link_bandwidth_test/link_bandwidth_test.go index 560170839e1..89fcfd03cb3 100644 --- a/feature/bgp/policybase/otg_tests/link_bandwidth_test/link_bandwidth_test.go +++ b/feature/bgp/policybase/otg_tests/link_bandwidth_test/link_bandwidth_test.go @@ -46,21 +46,21 @@ const ( v42TrafficStart = "203.0.114.1" v43Route = "203.0.115.1" v43TrafficStart = "203.0.115.1" - v4RoutePrefix = uint32(24) + v4RoutePrefix = uint32(32) v61Route = "2001:db8:128:128::1" v61RouteOtg = "2001:db8:128:128::1" - v61RouteAdvertise = "2001:db8:128:128::1/64" + v61RouteAdvertise = "2001:db8:128:128::1/128" parseV4 = "203.0.0.0/16" v61TrafficStart = "2001:db8:128:128::1" v62Route = "2001:db8:128:129::1" - v62RouteAdvertise = "2001:db8:128:129::1/64" + v62RouteAdvertise = "2001:db8:128:129::1/128" v62RouteOtg = "2001:db8:128:129::1" v62TrafficStart = "2001:db8:128:129::1" v63Route = "2001:db8:128:130::1" - v63RouteAdvertise = "2001:db8:128:130::1/64" + v63RouteAdvertise = "2001:db8:128:130::1/128" v63RouteOtg = "2001:db8:128:130::1" v63TrafficStart = "2001:db8:128:130::1" - v6RoutePrefix = uint32(64) + v6RoutePrefix = uint32(128) dutAS = uint32(32001) ateAS = uint32(32002) bgpName = "BGP" @@ -128,7 +128,13 @@ var ( extCommunitySetJuniper = map[string]string{ "linkbw_1M": "link-bandwidth:23456:1M", "linkbw_2G": "link-bandwidth:23456:2G", - "linkbw_any": "^link-bandwidth:.*", + "linkbw_any": "bandwidth-non-transitive:.*:.*", + } + + extCommunitySetArista = map[string]string{ + "linkbw_1M": "link-bandwidth:23456:1M", + "linkbw_2G": "link-bandwidth:23456:2G", + "linkbw_any": "^lbw:.*:.*$", } communitySet = map[string]string{ @@ -665,10 +671,19 @@ func configureExtCommunityRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { root := &oc.Root{} var communitySetCLIConfig string var extCommunitySetCLIConfig string + + switch dut.Vendor() { + case ondatra.CISCO: + extCommunitySet = extCommunitySetCisco + case ondatra.JUNIPER: + extCommunitySet = extCommunitySetJuniper + case ondatra.ARISTA: + extCommunitySet = extCommunitySetArista + } + if deviations.BgpExtendedCommunitySetUnsupported(dut) { switch dut.Vendor() { case ondatra.CISCO: - extCommunitySet = extCommunitySetCisco for name, community := range extCommunitySet { if name == "linkbw_any" && deviations.CommunityMemberRegexUnsupported(dut) { communitySetCLIConfig = fmt.Sprintf("community-set %v \n dfa-regex '%v' \n end-set", name, community) @@ -682,9 +697,6 @@ func configureExtCommunityRoutingPolicy(t *testing.T, dut *ondatra.DUTDevice) { t.Fatalf("Unsupported vendor %s for native command support for deviation 'BgpExtendedCommunitySetUnsupported'", dut.Vendor()) } } else { - if dut.Vendor() == ondatra.JUNIPER { - extCommunitySet = extCommunitySetJuniper - } for name, community := range extCommunitySet { rp := root.GetOrCreateRoutingPolicy() pdef := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets() diff --git a/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto b/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto index 65e51e6e5d7..faebf51779f 100644 --- a/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/nested_policies/metadata.textproto @@ -34,6 +34,7 @@ platform_exceptions: { } deviations: { bgp_rib_oc_path_unsupported: true + bgp_set_med_action_unsupported: true } } platform_exceptions: { diff --git a/feature/bgp/policybase/otg_tests/nested_policies/nested_policies_test.go b/feature/bgp/policybase/otg_tests/nested_policies/nested_policies_test.go index 7d065bf1490..070ad5b77ec 100644 --- a/feature/bgp/policybase/otg_tests/nested_policies/nested_policies_test.go +++ b/feature/bgp/policybase/otg_tests/nested_policies/nested_policies_test.go @@ -37,16 +37,16 @@ import ( const ( ipv4PrefixLen = 30 ipv6PrefixLen = 126 - v41Route = "203.0.113.0" + v41Route = "203.0.113.1" v41TrafficStart = "203.0.113.1" - v42Route = "198.51.100.0" + v42Route = "198.51.100.1" v42TrafficStart = "198.51.100.1" - v4RoutePrefix = uint32(24) - v61Route = "2001:db8:128:128::" + v4RoutePrefix = uint32(32) + v61Route = "2001:db8:128:128::1" v61TrafficStart = "2001:db8:128:128::1" - v62Route = "2001:db8:128:129::" + v62Route = "2001:db8:128:129::1" v62TrafficStart = "2001:db8:128:129::1" - v6RoutePrefix = uint32(64) + v6RoutePrefix = uint32(128) dutAS = uint32(65656) ateAS1 = uint32(65657) ateAS2 = uint32(65658) @@ -226,9 +226,13 @@ func TestBGPNestedPolicies(t *testing.T) { tc.validate(t, dut, ate) if tc.ipv4 { createFlow(t, td, tc.flowConfig) + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) checkTraffic(t, td, v4Flow) } else { createFlowV6(t, td, tc.flowConfig) + td.verifyDUTBGPEstablished(t) + td.verifyOTGBGPEstablished(t) checkTraffic(t, td, v6Flow) } }) diff --git a/feature/bgp/policybase/otg_tests/prefix_set_test/README.md b/feature/bgp/policybase/otg_tests/prefix_set_test/README.md index 7b4c849942a..dda275e70c5 100644 --- a/feature/bgp/policybase/otg_tests/prefix_set_test/README.md +++ b/feature/bgp/policybase/otg_tests/prefix_set_test/README.md @@ -19,165 +19,168 @@ BGP policy configuration with prefix-set matching * IPv4-prefix-set-1 - exact match on 10.23.15.0/26 * IPv4-prefix-set-2 - match on 10.23.0.0/16 * [TODO] IPv4-prefix-set-3 - match on 10.23.15.0/26, 10.23.17.0/26 - ``` - { - "openconfig-routing-policy:routing-policy": { + +#### Canonical OC +```json +{ + "routing-policy": { "defined-sets": { - "prefix-sets": { - "prefix-set": [ - { - "name": "IPV4-prefix-set-1", - "config": { - "name": "IPV4-prefix-set-1", - "mode": "IPV4" - }, - "prefixes": { - "prefix": [ - { - "ip-prefix": "10.23.15.0/26", - "masklength-range": "exact", - "config": { - "ip-prefix": "10.23.15.0/26", - "masklength-range": "exact" - } - } - ] - } - }, - { - "name": "IPV4-prefix-set-2", - "config": { - "name": "IPV4-prefix-set-2", - "mode": "IPV4" - }, - "prefixes": { - "prefix": [ + "prefix-sets": { + "prefix-set": [ { - "ip-prefix": "10.23.0.0/16", - "masklength-range": "exact", - "config": { - "ip-prefix": "10.23.0.0/16", - "masklength-range": "exact" - } - } - ] - } - }, - { - "name": "IPV4-prefix-set-3", - "config": { - "name": "IPV4-prefix-set-3", - "mode": "IPV4" - }, - "prefixes": { - "prefix": [ + "config": { + "mode": "IPV4", + "name": "IPv4-prefix-set-1" + }, + "name": "IPv4-prefix-set-1", + "prefixes": { + "prefix": [ + { + "config": { + "ip-prefix": "10.23.15.0/26", + "masklength-range": "exact" + }, + "ip-prefix": "10.23.15.0/26", + "masklength-range": "exact" + } + ] + } + }, { - "ip-prefix": "10.23.15.0/26", - "masklength-range": "exact", - "config": { - "ip-prefix": "10.23.15.0/26", - "masklength-range": "exact" - } + "config": { + "mode": "IPV4", + "name": "IPv4-prefix-set-2" + }, + "name": "IPv4-prefix-set-2", + "prefixes": { + "prefix": [ + { + "config": { + "ip-prefix": "10.23.0.0/16", + "masklength-range": "16..32" + }, + "ip-prefix": "10.23.0.0/16", + "masklength-range": "16..32" + } + ] + } }, { - "ip-prefix": "10.23.17.0/26", - "masklength-range": "exact", - "config": { - "ip-prefix": "10.23.17.0/26", - "masklength-range": "exact" - } + "config": { + "mode": "IPV4", + "name": "IPv4-prefix-set-3" + }, + "name": "IPv4-prefix-set-3", + "prefixes": { + "prefix": [ + { + "config": { + "ip-prefix": "10.23.15.0/26", + "masklength-range": "exact" + }, + "ip-prefix": "10.23.15.0/26", + "masklength-range": "exact" + }, + { + "config": { + "ip-prefix": "10.23.17.0/26", + "masklength-range": "exact" + }, + "ip-prefix": "10.23.17.0/26", + "masklength-range": "exact" + } + ] + } } - ] - } - } - ] - } + ] + } } - } } - ``` +} +``` * For IPv6: * Create three prefix-sets as below: * IPv6-prefix-set-1 - exact match on 2001:4860:f804::/48 * IPv6-prefix-set-2 - 65-128 match on ::/0 * [TODO] IPv6-prefix-set-3 - exact match on 2001:4860:f804::/48, 2001:4860:f806::/48 - ``` - { - "openconfig-routing-policy:routing-policy": { +#### Canonical OC +```json +{ + "routing-policy": { "defined-sets": { - "prefix-sets": { - "prefix-set": [ - { - "name": "IPV6-prefix-set-1", - "config": { - "name": "IPV6-prefix-set-1", - "mode": "IPV6" - }, - "prefixes": { - "prefix": [ - { - "ip-prefix": "2001:4860:f804::/48", - "masklength-range": "exact", - "config": { - "ip-prefix": "2001:4860:f804::/48", - "masklength-range": "exact" - } - } - ] - } - }, - { - "name": "IPV6-prefix-set-2", - "config": { - "name": "IPV6-prefix-set-2", - "mode": "IPV6" - }, - "prefixes": { - "prefix": [ + "prefix-sets": { + "prefix-set": [ { - "ip-prefix": "::/0", - "masklength-range": "65..128", - "config": { - "ip-prefix": "::/0", - "masklength-range": "65..128" - } - } - ] - } - }, - { - "name": "IPV6-prefix-set-3", - "config": { - "name": "IPV6-prefix-set-3", - "mode": "IPV6" - }, - "prefixes": { - "prefix": [ + "name": "IPV6-prefix-set-1", + "config": { + "name": "IPV6-prefix-set-1", + "mode": "IPV6" + }, + "prefixes": { + "prefix": [ + { + "ip-prefix": "2001:4860:f804::/48", + "masklength-range": "exact", + "config": { + "ip-prefix": "2001:4860:f804::/48", + "masklength-range": "exact" + } + } + ] + } + }, { - "ip-prefix": "2001:4860:f804::/48", - "masklength-range": "exact", - "config": { - "ip-prefix": "2001:4860:f804::/48", - "masklength-range": "exact" - } + "name": "IPV6-prefix-set-2", + "config": { + "name": "IPV6-prefix-set-2", + "mode": "IPV6" + }, + "prefixes": { + "prefix": [ + { + "ip-prefix": "::/0", + "masklength-range": "65..128", + "config": { + "ip-prefix": "::/0", + "masklength-range": "65..128" + } + } + ] + } }, { - "ip-prefix": "2001:4860:f806::/48", - "masklength-range": "exact", - "config": { - "ip-prefix": "2001:4860:f806::/48", - "masklength-range": "exact" - } + "name": "IPv6-prefix-set-3", + "config": { + "name": "IPv6-prefix-set-3", + "mode": "IPV6" + }, + "prefixes": { + "prefix": [ + { + "ip-prefix": "2001:4860:f804::/48", + "masklength-range": "exact", + "config": { + "ip-prefix": "2001:4860:f804::/48", + "masklength-range": "exact" + } + }, + { + "ip-prefix": "2001:4860:f806::/48", + "masklength-range": "exact", + "config": { + "ip-prefix": "2001:4860:f806::/48", + "masklength-range": "exact" + } + } + ] + } } - ] - } - } - ] - } + ] + } } - } } - ``` +} +``` ### RT-1.33.1 mach with option ANY * For IPv4 and IPv6: * Configure BGP policy on DUT to allow routes based on IPv4-prefix-set-2 and reject routes based on IPv4-prefix-set-1 @@ -190,186 +193,187 @@ BGP policy configuration with prefix-set matching * Test configuration * Generate new policies (bgpInvertIPv4, bgpInvertPv6) * Configure BGP policy on DUT to reject IPv4 routes that are NOT covered in IPv4-prefix-set-3 using `INVERT` match-type-option; Allow any other IPv4 route. - ``` - { - "openconfig-routing-policy:routing-policy": { - "policy-definitions": { + +```json +{ + "routing-policy": { + "policy-definitions": { "policy-definition": [ - { - "name": "bgpInvertIPv4", - "config": { - "name": "bgpInvertIPv4" - }, - "statements": { - "statement": [ - { - "name": "stm1", - "openconfig-routing-policy:config": { - "name": "stm1" - }, - "openconfig-routing-policy:conditions": { - "match-prefix-set": { - "config": { - "prefix-set": "IPV4-prefix-set-3", - "match-set-options": "INVERT" - } - } - }, - "openconfig-routing-policy:actions": { - "config": { - "policy-result": "REJECT_ROUTE" - } - } + { + "name": "bgpInvertIPv4", + "config": { + "name": "bgpInvertIPv4" }, - { - "name": "stm2", - "openconfig-routing-policy:config": { - "name": "stm2" - }, - "openconfig-routing-policy:actions": { - "config": { - "policy-result": "ACCEPT_ROUTE" - } - } + "statements": { + "statement": [ + { + "name": "stm1", + "openconfig-routing-policy:config": { + "name": "stm1" + }, + "openconfig-routing-policy:conditions": { + "match-prefix-set": { + "config": { + "prefix-set": "IPv4-prefix-set-3", + "match-set-options": "INVERT" + } + } + }, + "openconfig-routing-policy:actions": { + "config": { + "policy-result": "REJECT_ROUTE" + } + } + }, + { + "name": "stm2", + "openconfig-routing-policy:config": { + "name": "stm2" + }, + "openconfig-routing-policy:actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + } + } + ] } - ] } - } ] - } } - } - ``` + } +} +``` * Configure BGP policy on DUT to reject IPv6 routes that are NOT covered in IPv6-prefix-set-3 using `INVERT` match-type-option; Allow any other IPv6 route. - ``` - { - "openconfig-routing-policy:routing-policy": { - "policy-definitions": { + +```json +{ + "routing-policy": { + "policy-definitions": { "policy-definition": [ - { - "name": "bgpInvertIPV6", - "config": { - "name": "bgpInvertIPV6" - }, - "statements": { - "statement": [ - { - "name": "stm1", - "openconfig-routing-policy:config": { - "name": "stm1" - }, - "openconfig-routing-policy:conditions": { - "match-prefix-set": { - "config": { - "prefix-set": "IPV6-prefix-set-3", - "match-set-options": "INVERT" - } - } - }, - "openconfig-routing-policy:actions": { - "config": { - "policy-result": "REJECT_ROUTE" - } - } + { + "name": "bgpInvertIPV6", + "config": { + "name": "bgpInvertIPV6" }, - { - "name": "stm2", - "openconfig-routing-policy:config": { - "name": "stm2" - }, - "openconfig-routing-policy:actions": { - "config": { - "policy-result": "ACCEPT_ROUTE" - } - } + "statements": { + "statement": [ + { + "name": "stm1", + "openconfig-routing-policy:config": { + "name": "stm1" + }, + "openconfig-routing-policy:conditions": { + "match-prefix-set": { + "config": { + "prefix-set": "IPv6-prefix-set-3", + "match-set-options": "INVERT" + } + } + }, + "openconfig-routing-policy:actions": { + "config": { + "policy-result": "REJECT_ROUTE" + } + } + }, + { + "name": "stm2", + "openconfig-routing-policy:config": { + "name": "stm2" + }, + "openconfig-routing-policy:actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + } + } + ] } - ] } - } ] - } } - } - ``` + } +} +``` * Attach bgpInvertIPv4, bgpInvertIPv6 as import policies to DUT port-1 eBGP session - ``` - { - "openconfig-network-instance:network-instances": { + +```json +{ + "network-instances": { "network-instance": [ - { - "name": "DEFAULT", - "config": { - "name": "DEFAULT" - }, - "protocols": { - "protocol": [ - { - "identifier": "openconfig-policy-types:BGP", - "name": "DEFAULT", - "config": { - "identifier": "openconfig-policy-types:BGP", + { + "name": "DEFAULT", + "config": { "name": "DEFAULT" - }, - "bgp": { - "neighbors": { - "neighbor": [ - { - "neighbor-address": "", - "config": { - "neighbor-address": "" - }, - "afi-safis": { - "afi-safi": [ - { - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST" - }, - "apply-policy": { - "config": { - "import-policy": "bgpInvertIPv4", - "default-import-policy": "ACCEPT_ROUTE", - "defaul-export-policy": "ACCEPT_ROUTE" - } - } - } - ] - } - }, + }, + "protocols": { + "protocol": [ { - "neighbor-address": "", - "config": { - "neighbor-address": "" - }, - "afi-safis": { - "afi-safi": [ - { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST" - }, - "apply-policy": { - "config": { - "import-policy": "bgpInvertIPv6", - "default-import-policy": "ACCEPT_ROUTE", - "defaul-export-policy": "ACCEPT_ROUTE" - } + "identifier": "openconfig-policy-types:BGP", + "name": "DEFAULT", + "config": { + "identifier": "openconfig-policy-types:BGP", + "name": "DEFAULT" + }, + "bgp": { + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.0.2.2", + "config": { + "neighbor-address": "192.0.2.2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST" + }, + "apply-policy": { + "config": { + "default-import-policy": "ACCEPT_ROUTE", + "default-export-policy": "ACCEPT_ROUTE" + } + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::192:0:2:2", + "config": { + "neighbor-address": "2001:db8::192:0:2:2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST" + }, + "apply-policy": { + "config": { + "default-import-policy": "ACCEPT_ROUTE", + "default-export-policy": "ACCEPT_ROUTE" + } + } + } + ] + } + } + ] } - } - ] - } + } } - ] - } - } + ] } - ] } - } ] - } } - ``` +} +``` * Push the generated configuration to the DUT using gnmi.Set with replace option. * Advertise from OTG port-1 BGP prefixes: * 10.23.15.0/26, 10.23.16.0/26, 10.23.17.0/26 @@ -382,84 +386,83 @@ BGP policy configuration with prefix-set matching * Test configuration * Generate the same config as for RT-1.33.2 above, with following modification: * Attach bgpInvertIPv4, bgpInvertIPv6 as export policies to DUT port-1 eBGP session - ``` - { - "openconfig-network-instance:network-instances": { + +```json +{ + "network-instances": { "network-instance": [ - { - "name": "DEFAULT", - "config": { - "name": "DEFAULT" - }, - "protocols": { - "protocol": [ - { - "identifier": "openconfig-policy-types:BGP", - "name": "DEFAULT", - "config": { - "identifier": "openconfig-policy-types:BGP", + { + "name": "DEFAULT", + "config": { "name": "DEFAULT" - }, - "bgp": { - "neighbors": { - "neighbor": [ - { - "neighbor-address": "", - "config": { - "neighbor-address": "" - }, - "afi-safis": { - "afi-safi": [ - { - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST" - }, - "apply-policy": { - "config": { - "import-policy": "bgpInvertIPv4", - "default-import-policy": "ACCEPT_ROUTE", - "defaul-export-policy": "ACCEPT_ROUTE" - } - } - } - ] - } - }, + }, + "protocols": { + "protocol": [ { - "neighbor-address": "", - "config": { - "neighbor-address": "" - }, - "afi-safis": { - "afi-safi": [ - { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST" - }, - "apply-policy": { - "config": { - "import-policy": "bgpInvertIPv6", - "default-import-policy": "ACCEPT_ROUTE", - "defaul-export-policy": "ACCEPT_ROUTE" - } + "identifier": "openconfig-policy-types:BGP", + "name": "DEFAULT", + "config": { + "identifier": "openconfig-policy-types:BGP", + "name": "DEFAULT" + }, + "bgp": { + "neighbors": { + "neighbor": [ + { + "neighbor-address": "192.0.2.2", + "config": { + "neighbor-address": "192.0.2.2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST" + }, + "apply-policy": { + "config": { + "default-import-policy": "ACCEPT_ROUTE", + "default-export-policy": "ACCEPT_ROUTE" + } + } + } + ] + } + }, + { + "neighbor-address": "2001:db8::192:0:2:2", + "config": { + "neighbor-address": "2001:db8::192:0:2:2" + }, + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST" + }, + "apply-policy": { + "config": { + "default-import-policy": "ACCEPT_ROUTE", + "default-export-policy": "ACCEPT_ROUTE" + } + } + } + ] + } + } + ] } - } - ] - } + } } - ] - } - } + ] } - ] } - } ] - } } - ``` +} +``` * Push the generated configuration to the DUT using gnmi.Set with replace option. * Behaviour validation * use the same validation as for RT-1.33.2 above diff --git a/feature/bgp/policybase/otg_tests/prefix_set_test/bgp_prefix_set_test.go b/feature/bgp/policybase/otg_tests/prefix_set_test/bgp_prefix_set_test.go index 2f9036ad1a7..bb20126bb3e 100644 --- a/feature/bgp/policybase/otg_tests/prefix_set_test/bgp_prefix_set_test.go +++ b/feature/bgp/policybase/otg_tests/prefix_set_test/bgp_prefix_set_test.go @@ -385,7 +385,7 @@ func validatePrefixCount(t *testing.T, dut *ondatra.DUTDevice, nbr bgpNeighbor, prefixPath := statePath.Neighbor(nbr.nbrAddr).AfiSafi(nbr.afiSafi).Prefixes() // Waiting for Installed count to get updated after session comes up or policy is applied - gotInstalled, ok := gnmi.Watch(t, dut, prefixPath.Installed().State(), 40*time.Second, func(val *ygnmi.Value[uint32]) bool { // increased wait time to 20s from 10s + gotInstalled, ok := gnmi.Watch(t, dut, prefixPath.Installed().State(), 120*time.Second, func(val *ygnmi.Value[uint32]) bool { gotInstalled, _ := val.Val() t.Logf("Prefix that are installed %v and want %v", gotInstalled, wantInstalled) return gotInstalled == wantInstalled @@ -395,7 +395,7 @@ func validatePrefixCount(t *testing.T, dut *ondatra.DUTDevice, nbr bgpNeighbor, } // Waiting for Received count to get updated after session comes up or policy is applied - gotRx, ok := gnmi.Watch(t, dut, prefixPath.ReceivedPrePolicy().State(), 40*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotRx, ok := gnmi.Watch(t, dut, prefixPath.ReceivedPrePolicy().State(), 60*time.Second, func(val *ygnmi.Value[uint32]) bool { gotRx, _ := val.Val() t.Logf("Prefix that are received %v and want %v", gotRx, wantRx) return gotRx == wantRx @@ -405,7 +405,7 @@ func validatePrefixCount(t *testing.T, dut *ondatra.DUTDevice, nbr bgpNeighbor, } // Waiting for Sent count to get updated after session comes up or policy is applied - gotSent, ok := gnmi.Watch(t, dut, prefixPath.Sent().State(), 40*time.Second, func(val *ygnmi.Value[uint32]) bool { + gotSent, ok := gnmi.Watch(t, dut, prefixPath.Sent().State(), 120*time.Second, func(val *ygnmi.Value[uint32]) bool { t.Logf("Prefix that are sent %v", prefixPath.Sent().State()) gotSent, _ := val.Val() t.Logf("Prefix that are sent %v and want %v", gotSent, wantSent) diff --git a/feature/bgp/policybase/otg_tests/route_installation_test/metadata.textproto b/feature/bgp/policybase/otg_tests/route_installation_test/metadata.textproto index 811e8310e32..7d854b27ffd 100644 --- a/feature/bgp/policybase/otg_tests/route_installation_test/metadata.textproto +++ b/feature/bgp/policybase/otg_tests/route_installation_test/metadata.textproto @@ -34,3 +34,11 @@ platform_exceptions: { default_network_instance: "default" } } +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + bgp_set_med_action_unsupported: true + } +} diff --git a/feature/bgp/policybase/otg_tests/statement_insertion_removal/statement_insertion_removal_test.go b/feature/bgp/policybase/otg_tests/statement_insertion_removal/statement_insertion_removal_test.go index fbffd03da8c..9d07c54c030 100644 --- a/feature/bgp/policybase/otg_tests/statement_insertion_removal/statement_insertion_removal_test.go +++ b/feature/bgp/policybase/otg_tests/statement_insertion_removal/statement_insertion_removal_test.go @@ -15,7 +15,6 @@ package statement_insertion_removal_test import ( - "fmt" "testing" "time" @@ -36,13 +35,29 @@ func TestMain(m *testing.M) { } const ( - peerGrpName1 = "BGP-PEER-GROUP1" - dutAS = 65501 - ateAS1 = 65502 - plenIPv4 = 30 - policyName = "Test-Policy" + peerGrpName = "BGP-PEER-GROUP1" + dutAS = 65501 + ateAS = 65502 + plenIPv4 = 30 + policyName = "Test-Policy" ) +type communitySet struct { + statement string + name string + members string +} + +type bgpNeighbor struct { + as uint32 + nbrAddr string + afiSafi oc.E_BgpTypes_AFI_SAFI_TYPE + peerGrp string +} + +// communitySetsMap is a map of configuration type to a slice of communitySet. +type communitySetsMap map[string][]communitySet + var ( dutPort1 = attrs.Attributes{ Desc: "DUT to ATE Port1", @@ -58,178 +73,96 @@ var ( ebgp1NbrV4 = &bgpNeighbor{ nbrAddr: atePort1.IPv4, afiSafi: oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST, - as: ateAS1, - peerGrp: peerGrpName1} + as: ateAS, + peerGrp: peerGrpName} ebgpNbrs = []*bgpNeighbor{ebgp1NbrV4} - communitySetsInitial = []communitySet{ + communitySets = []communitySet{ { - name: "Comm_100_1", - members: "100:1", + // This is just a placeholder communitySet, do not use. + statement: "Stmnt_0", + name: "Comm_100_0", + members: "100:0", }, { - name: "Comm_100_3", - members: "100:3", + statement: "Stmnt_1", + name: "Comm_100_1", + members: "100:1", }, { - name: "Comm_100_5", - members: "100:5", + statement: "Stmnt_2", + name: "Comm_100_2", + members: "100:2", }, { - name: "Comm_100_7", - members: "100:7", + statement: "Stmnt_3", + name: "Comm_100_3", + members: "100:3", }, { - name: "Comm_100_9", - members: "100:9", + statement: "Stmnt_4", + name: "Comm_100_4", + members: "100:4", }, - } - communitySetsInsertion = []communitySet{ { - name: "Comm_100_1", - members: "100:1", + statement: "Stmnt_5", + name: "Comm_100_5", + members: "100:5", }, { - name: "Comm_100_2", - members: "100:2", + statement: "Stmnt_6", + name: "Comm_100_6", + members: "100:6", }, { - name: "Comm_100_3", - members: "100:3", + statement: "Stmnt_7", + name: "Comm_100_7", + members: "100:7", }, { - name: "Comm_100_5", - members: "100:5", + statement: "Stmnt_8", + name: "Comm_100_8", + members: "100:8", }, { - name: "Comm_100_7", - members: "100:7", + statement: "Stmnt_9", + name: "Comm_100_9", + members: "100:9", }, { - name: "Comm_100_9", - members: "100:9", - }, - } - communitySetsRemoval = []communitySet{ - { - name: "Comm_100_1", - members: "100:1", + statement: "Stmnt_10", + name: "Comm_100_10", + members: "100:10", }, { - name: "Comm_100_2", - members: "100:2", - }, - { - name: "Comm_100_3", - members: "100:3", - }, - { - name: "Comm_100_7", - members: "100:7", - }, - { - name: "Comm_100_9", - members: "100:9", + statement: "Stmnt_11", + name: "Comm_100_11", + members: "100:11", }, } - communitySetsReInsertion = []communitySet{ - { - name: "Comm_100_1", - members: "100:1", - }, - { - name: "Comm_100_2", - members: "100:2", - }, - { - name: "Comm_100_3", - members: "100:3", - }, - { - name: "Comm_100_4", - members: "100:4", - }, - { - name: "Comm_100_5", - members: "100:5", + + communitySetsConfigurations = communitySetsMap{ + "initial": []communitySet{ + communitySets[1], communitySets[3], communitySets[5], communitySets[7], communitySets[9], }, - { - name: "Comm_100_6", - members: "100:6", + "insertion": []communitySet{ + communitySets[1], communitySets[2], communitySets[3], communitySets[5], communitySets[7], + communitySets[9], }, - { - name: "Comm_100_7", - members: "100:7", + "removal": []communitySet{ + communitySets[1], communitySets[2], communitySets[3], communitySets[7], communitySets[9], }, - { - name: "Comm_100_8", - members: "100:8", - }, - { - name: "Comm_100_9", - members: "100:9", + "re-insertion": []communitySet{ + communitySets[1], communitySets[2], communitySets[3], communitySets[4], communitySets[5], + communitySets[6], communitySets[7], communitySets[8], communitySets[9], communitySets[10], }, - { - name: "Comm_100_10", - members: "100:10", - }, - } - communitySetsEdit = []communitySet{ - { - name: "Comm_100_11", - members: "100:11", - }, - { - name: "Comm_100_2", - members: "100:2", - }, - { - name: "Comm_100_3", - members: "100:3", - }, - { - name: "Comm_100_4", - members: "100:4", - }, - { - name: "Comm_100_5", - members: "100:5", - }, - { - name: "Comm_100_6", - members: "100:6", - }, - { - name: "Comm_100_7", - members: "100:7", - }, - { - name: "Comm_100_8", - members: "100:8", - }, - { - name: "Comm_100_9", - members: "100:9", - }, - { - name: "Comm_100_10", - members: "100:10", + "edit": []communitySet{ + communitySets[11], communitySets[2], communitySets[3], communitySets[4], communitySets[5], + communitySets[6], communitySets[7], communitySets[8], communitySets[9], communitySets[10], }, } ) -type communitySet struct { - name string - members string -} - -type bgpNeighbor struct { - as uint32 - nbrAddr string - afiSafi oc.E_BgpTypes_AFI_SAFI_TYPE - peerGrp string -} - func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() dc := gnmi.OC() @@ -246,182 +179,32 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { } } -func createRoutePolicyInitial(t *testing.T, dut *ondatra.DUTDevice) *oc.RoutingPolicy { +func createRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, communitySets []communitySet) *oc.RoutingPolicy { t.Helper() d := &oc.Root{} - var pdef *oc.RoutingPolicy_PolicyDefinition rp := d.GetOrCreateRoutingPolicy() - pdef = rp.GetOrCreatePolicyDefinition("Test-Policy") - for _, communitySet := range communitySetsInitial { + pdef := rp.GetOrCreatePolicyDefinition(policyName) + + for idx := range communitySets { + communitySet := &communitySets[idx] + commSet := rp.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySet.name) - var commMemberUnion []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union - commMemberUnion = append(commMemberUnion, oc.UnionString(communitySet.members)) + commMemberUnion := []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString(communitySet.members)} commSet.SetCommunityMember(commMemberUnion) + + stmt, _ := pdef.AppendNewStatement(communitySet.statement) + matchCommunitySet := stmt.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet() + matchCommunitySet.SetCommunitySet(communitySet.name) + matchCommunitySet.SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) + stmt.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT } - stmt1, _ := pdef.AppendNewStatement("Stmnt_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt1.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt3, _ := pdef.AppendNewStatement("Stmnt_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt3.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt5, _ := pdef.AppendNewStatement("Stmnt_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt5.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt7, _ := pdef.AppendNewStatement("Stmnt_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt7.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt9, _ := pdef.AppendNewStatement("Stmnt_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt9.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT stmtLast, _ := pdef.AppendNewStatement("Stmnt_Last") stmtLast.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) - pathV4 := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().PeerGroup(peerGrpName1).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() - policyV4 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").GetOrCreateBgp().GetOrCreatePeerGroup(peerGrpName1).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() - policyV4.SetImportPolicy([]string{"Test-Policy"}) - policyV4.SetExportPolicy([]string{"Test-Policy"}) - gnmi.Update(t, dut, pathV4.Config(), policyV4) return rp } -func createInsertionPolicy(t *testing.T, dut *ondatra.DUTDevice) *oc.RoutingPolicy { - t.Helper() - d := &oc.Root{} - rpi := d.GetOrCreateRoutingPolicy() - pdef := rpi.GetOrCreatePolicyDefinition("Test-Policy") - for _, communitySet := range communitySetsInsertion { - commSet := rpi.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySet.name) - var commMemberUnion []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union - commMemberUnion = append(commMemberUnion, oc.UnionString(communitySet.members)) - commSet.SetCommunityMember(commMemberUnion) - } - stmt1, _ := pdef.AppendNewStatement("Stmnt_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt1.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt2, _ := pdef.AppendNewStatement("Stmnt_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt2.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt3, _ := pdef.AppendNewStatement("Stmnt_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt3.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt5, _ := pdef.AppendNewStatement("Stmnt_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt5.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt7, _ := pdef.AppendNewStatement("Stmnt_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt7.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt9, _ := pdef.AppendNewStatement("Stmnt_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt9.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmtLast, _ := pdef.AppendNewStatement("Stmnt_Last") - stmtLast.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - return rpi -} - -func createRemovalPolicy(t *testing.T, dut *ondatra.DUTDevice) *oc.RoutingPolicy { - t.Helper() - d := &oc.Root{} - rpr := d.GetOrCreateRoutingPolicy() - pdef := rpr.GetOrCreatePolicyDefinition("Test-Policy") - for _, communitySet := range communitySetsRemoval { - commSet := rpr.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySet.name) - var commMemberUnion []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union - commMemberUnion = append(commMemberUnion, oc.UnionString(communitySet.members)) - commSet.SetCommunityMember(commMemberUnion) - } - stmt1, _ := pdef.AppendNewStatement("Stmnt_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt1.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt2, _ := pdef.AppendNewStatement("Stmnt_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt2.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt3, _ := pdef.AppendNewStatement("Stmnt_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt3.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt7, _ := pdef.AppendNewStatement("Stmnt_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt7.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt9, _ := pdef.AppendNewStatement("Stmnt_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt9.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmtLast, _ := pdef.AppendNewStatement("Stmnt_Last") - stmtLast.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - return rpr -} -func createReInsertionPolicy(t *testing.T, dut *ondatra.DUTDevice) *oc.RoutingPolicy { - t.Helper() - d := &oc.Root{} - rpre := d.GetOrCreateRoutingPolicy() - pdef := rpre.GetOrCreatePolicyDefinition("Test-Policy") - for _, communitySet := range communitySetsReInsertion { - commSet := rpre.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySet.name) - var commMemberUnion []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union - commMemberUnion = append(commMemberUnion, oc.UnionString(communitySet.members)) - commSet.SetCommunityMember(commMemberUnion) - } - stmt1, _ := pdef.AppendNewStatement("Stmnt_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt1.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt2, _ := pdef.AppendNewStatement("Stmnt_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt2.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt3, _ := pdef.AppendNewStatement("Stmnt_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt3.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt4, _ := pdef.AppendNewStatement("Stmnt_4") - stmt4.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_4") - stmt4.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt4.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt5, _ := pdef.AppendNewStatement("Stmnt_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt5.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt6, _ := pdef.AppendNewStatement("Stmnt_6") - stmt6.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_6") - stmt6.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt6.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt7, _ := pdef.AppendNewStatement("Stmnt_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt7.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt8, _ := pdef.AppendNewStatement("Stmnt_8") - stmt8.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_8") - stmt8.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt8.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt9, _ := pdef.AppendNewStatement("Stmnt_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt9.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt10, _ := pdef.AppendNewStatement("Stmnt_10") - stmt10.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_10") - stmt10.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt10.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmtLast, _ := pdef.AppendNewStatement("Stmnt_Last") - stmtLast.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - return rpre -} - func bgpCreateNbr(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { // Configure BGP on DUT @@ -498,7 +281,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { // eBGP v4 session on Port1. iDut1Bgp := iDut1Dev.Bgp().SetRouterId(iDut1Ipv4.Address()) iDut1Bgp4Peer := iDut1Bgp.Ipv4Interfaces().Add().SetIpv4Name(iDut1Ipv4.Name()).Peers().Add().SetName(atePort1.Name + ".BGP4.peer") - iDut1Bgp4Peer.SetPeerAddress(iDut1Ipv4.Gateway()).SetAsNumber(ateAS1).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + iDut1Bgp4Peer.SetPeerAddress(iDut1Ipv4.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) iDut1Bgp4Peer.LearnedInformationFilter().SetUnicastIpv4Prefix(true) otg.PushConfig(t, config) @@ -506,86 +289,19 @@ func configureOTG(t *testing.T, otg *otg.OTG) gosnappi.Config { return config } -func editPolicyStatement(t *testing.T, dut *ondatra.DUTDevice, policy string) *oc.RoutingPolicy { - t.Helper() - d := &oc.Root{} - rpe := d.GetOrCreateRoutingPolicy() - pdef := rpe.GetOrCreatePolicyDefinition(policy) - for _, communitySet := range communitySetsEdit { - commSet := rpe.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySet.name) - var commMemberUnion []oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union - commMemberUnion = append(commMemberUnion, oc.UnionString(communitySet.members)) - commSet.SetCommunityMember(commMemberUnion) - } - - stmt1, _ := pdef.AppendNewStatement("Stmnt_1") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_11") - stmt1.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt1.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt2, _ := pdef.AppendNewStatement("Stmnt_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_2") - stmt2.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt2.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt3, _ := pdef.AppendNewStatement("Stmnt_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_3") - stmt3.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt3.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt4, _ := pdef.AppendNewStatement("Stmnt_4") - stmt4.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_4") - stmt4.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt4.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt5, _ := pdef.AppendNewStatement("Stmnt_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_5") - stmt5.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt5.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt6, _ := pdef.AppendNewStatement("Stmnt_6") - stmt6.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_6") - stmt6.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt6.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt7, _ := pdef.AppendNewStatement("Stmnt_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_7") - stmt7.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt7.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt8, _ := pdef.AppendNewStatement("Stmnt_8") - stmt8.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_8") - stmt8.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt8.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt9, _ := pdef.AppendNewStatement("Stmnt_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_9") - stmt9.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt9.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmt10, _ := pdef.AppendNewStatement("Stmnt_10") - stmt10.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetCommunitySet("Comm_100_10") - stmt10.GetOrCreateConditions().GetOrCreateBgpConditions().GetOrCreateMatchCommunitySet().SetMatchSetOptions(oc.RoutingPolicy_MatchSetOptionsType_ANY) - stmt10.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_NEXT_STATEMENT - stmtLast, _ := pdef.AppendNewStatement("Stmnt_Last") - stmtLast.GetOrCreateActions().PolicyResult = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE - return rpe -} -func isRoutingPolicyEqual(t *testing.T, dut *ondatra.DUTDevice, policy1 string, policy2 *oc.RoutingPolicy) bool { - - fmt.Printf("Inside isRoutingPolicyEqual") +func isRoutingPolicyEqual(t *testing.T, dut *ondatra.DUTDevice, importPolicy string, rpWant *oc.RoutingPolicy) bool { - pdef1 := gnmi.Get(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinition(policy1).Config()) - pdef2 := policy2.GetPolicyDefinition("Test-Policy") - fmt.Printf("Iterate through the statements in the policy definition") - fmt.Printf("pdef2: %v, pdef1: %v", pdef2.Name, pdef1.Name) + pdefGot := gnmi.Get(t, dut, gnmi.OC().RoutingPolicy().PolicyDefinition(importPolicy).Config()) + pdefWant := rpWant.GetPolicyDefinition(policyName) - fmt.Printf("pdef2.Statement.Keys(): %v, pdef1.Statement.Keys(): %v", pdef2.Statement.Keys(), pdef1.Statement.Keys()) - if len(pdef2.Statement.Keys()) != len(pdef1.Statement.Keys()) { + if len(pdefGot.Statement.Keys()) != len(pdefWant.Statement.Keys()) { return false } - for _, stmt := range pdef2.Statement.Keys() { - // cross check with communitySetsInitial - found := false - - // check if the community set name matches. - commSet2 := pdef2.Statement.Get(stmt).GetConditions().GetBgpConditions().GetMatchCommunitySet().GetCommunitySet() - commSet1 := pdef1.Statement.Get(stmt).GetConditions().GetBgpConditions().GetMatchCommunitySet().GetCommunitySet() - if commSet1 == commSet2 { - found = true - } - if !found { + for _, stmt := range pdefWant.Statement.Keys() { + // Check if the community set name matches. + got := pdefGot.Statement.Get(stmt).GetConditions().GetBgpConditions().GetMatchCommunitySet().GetCommunitySet() + want := pdefWant.Statement.Get(stmt).GetConditions().GetBgpConditions().GetMatchCommunitySet().GetCommunitySet() + if got != want { return false } } @@ -601,58 +317,60 @@ func TestStatementInsertionRemoval(t *testing.T) { dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") gnmi.Delete(t, dut, dutConfPath.Config()) - dutConf := bgpCreateNbr(dutAS, ateAS1, dut) + dutConf := bgpCreateNbr(dutAS, ateAS, dut) gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) configureOTG(t, otg) verifyBgpState(t, dut) - t.Run("Initial Policy", func(t *testing.T) { - rp := createRoutePolicyInitial(t, dut) - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName1).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + t.Run("initial_policy", func(t *testing.T) { + rp := createRoutePolicy(t, dut, communitySetsConfigurations["initial"]) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + pathV4 := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp().PeerGroup(peerGrpName).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).ApplyPolicy() + d := &oc.Root{} + policyV4 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").GetOrCreateBgp().GetOrCreatePeerGroup(peerGrpName).GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).GetOrCreateApplyPolicy() + policyV4.SetImportPolicy([]string{policyName}) + policyV4.SetExportPolicy([]string{policyName}) + gnmi.Update(t, dut, pathV4.Config(), policyV4) + + configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rp) { - t.Errorf("Configured import policy does not match the expected policy. \nGot:\n%v\nWant:\n%v", configuredPolicy, rp) + t.Errorf("isRoutingPolicyEqual(t, dut, importPolicy, rpWant):\n%v \nwant:\n%v", configuredPolicy, rp) } }) - - t.Run("Policy statement insertion", func(t *testing.T) { - rpi := createInsertionPolicy(t, dut) - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rpi) - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName1).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) - if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rpi) { - t.Errorf("Configured import policy does not match the expected policy. \nGot:\n%v\nWant:\n%v", configuredPolicy, rpi) + t.Run("policy_statement_insertion", func(t *testing.T) { + rp := createRoutePolicy(t, dut, communitySetsConfigurations["insertion"]) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) + if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rp) { + t.Errorf("isRoutingPolicyEqual(t, dut, importPolicy, rpWant):\n%v \nwant:\n%v", configuredPolicy, rp) } }) - t.Run("Policy statement removal", func(t *testing.T) { - rpr := createRemovalPolicy(t, dut) - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rpr) - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName1).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) - if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rpr) { - t.Errorf("Configured import policy does not match the expected policy. \nGot:\n%v\nWant:\n%v", configuredPolicy, rpr) + t.Run("policy_statement_removal", func(t *testing.T) { + rp := createRoutePolicy(t, dut, communitySetsConfigurations["removal"]) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) + if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rp) { + t.Errorf("isRoutingPolicyEqual(t, dut, importPolicy, rpWant):\n%v \nwant:\n%v", configuredPolicy, rp) } - }) - t.Run("Policy statement re-insertion ", func(t *testing.T) { - rpi := createReInsertionPolicy(t, dut) - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rpi) - rpre := createRemovalPolicy(t, dut) - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rpre) - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName1).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) - if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rpre) { - t.Errorf("Configured import policy does not match the expected policy. \nGot:\n%v\nWant:\n%v", configuredPolicy, rpre) + t.Run("policy_statement_re-insertion", func(t *testing.T) { + rp := createRoutePolicy(t, dut, communitySetsConfigurations["re-insertion"]) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + rp = createRoutePolicy(t, dut, communitySetsConfigurations["removal"]) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) + if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rp) { + t.Errorf("isRoutingPolicyEqual(t, dut, importPolicy, rpWant):\n%v \nwant:\n%v", configuredPolicy, rp) } }) - t.Run("Edit policy statement", func(t *testing.T) { - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") - configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName1).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) - rpe := editPolicyStatement(t, dut, configuredPolicy.ImportPolicy[0]) - gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rpe) - if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rpe) { - t.Errorf("Configured import policy does not match the expected policy. \nGot:\n%v\nWant:\n%v", configuredPolicy, rpe) + t.Run("edit_policy_statement", func(t *testing.T) { + rp := createRoutePolicy(t, dut, communitySetsConfigurations["edit"]) + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) + configuredPolicy := gnmi.Get(t, dut, bgpPath.Bgp().PeerGroup(peerGrpName).AfiSafi(ebgp1NbrV4.afiSafi).ApplyPolicy().State()) + if len(configuredPolicy.ImportPolicy) > 0 && !isRoutingPolicyEqual(t, dut, configuredPolicy.ImportPolicy[0], rp) { + t.Errorf("isRoutingPolicyEqual(t, dut, importPolicy, rpWant):\n%v \nwant:\n%v", configuredPolicy, rp) } - }) } diff --git a/feature/bgp/prefixlimit/feature.textproto b/feature/bgp/prefixlimit/feature.textproto deleted file mode 100644 index 0ecbb09d46a..00000000000 --- a/feature/bgp/prefixlimit/feature.textproto +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "bgp_prefixlimit" - version: 1 -} - -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/max-prefixes" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/warning-threshold-pct" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/restart-time" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/max-prefixes" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv6-unicast/prefix-limit/state/max-prefixes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv6-unicast/prefix-limit/state/warning-threshold-pct" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/restart-time" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/ipv6-unicast/prefix-limit/state/warning-threshold-pct" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/restart-time" -} diff --git a/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/README.md b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/README.md index e507230aa7a..4dc96970ce5 100644 --- a/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/README.md +++ b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/README.md @@ -37,6 +37,11 @@ rpcs: gNMI.Set: ``` +## Canonical OC +```json +{} +``` + ## Minimum DUT platform requirement vRX diff --git a/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go index 1594facc35b..cee019a4c96 100644 --- a/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go +++ b/feature/bgp/prefixlimit/otg_tests/bgp_prefix_limit_test/bgp_prefix_limit_test.go @@ -15,6 +15,7 @@ package bgp_prefix_limit_test import ( + "slices" "testing" "time" @@ -111,6 +112,7 @@ var ( IPv4Len: plenIPv4, IPv6Len: plenIPv6, } + kneDeviceModelList = []string{"ncptx"} ) // configureDUT configures all the interfaces and BGP on the DUT. @@ -194,12 +196,12 @@ func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { configureBGPv4Routes(dstBgp4Peer, dstIpv4.Address(), r4OverLimit, ipv4DstTraffic, prefixLimit+1) configureBGPv6Routes(dstBgp6Peer, dstIpv6.Address(), r6OverLimit, ipv6DstTraffic, prefixLimit+1) - configureFlow(topo, "IPv4.UnderLimit", srcIpv4.Name(), r4UnderLimit, ateSrc.MAC, ateSrc.IPv4, ipv4DstTraffic, "ipv4", prefixLimit-1) - configureFlow(topo, "IPv6.UnderLimit", srcIpv6.Name(), r6UnderLimit, ateSrc.MAC, ateSrc.IPv6, ipv6DstTraffic, "ipv6", prefixLimit-1) - configureFlow(topo, "IPv4.AtLimit", srcIpv4.Name(), r4AtLimit, ateSrc.MAC, ateSrc.IPv4, ipv4DstTraffic, "ipv4", prefixLimit) - configureFlow(topo, "IPv6.AtLimit", srcIpv6.Name(), r6AtLimit, ateSrc.MAC, ateSrc.IPv6, ipv6DstTraffic, "ipv6", prefixLimit) - configureFlow(topo, "IPv4.OverLimit", srcIpv4.Name(), r4OverLimit, ateSrc.MAC, ateSrc.IPv4, ipv4DstTraffic, "ipv4", prefixLimit+1) - configureFlow(topo, "IPv6.OverLimit", srcIpv6.Name(), r6OverLimit, ateSrc.MAC, ateSrc.IPv6, ipv6DstTraffic, "ipv6", prefixLimit+1) + configureFlow(t, topo, "IPv4.UnderLimit", srcIpv4.Name(), r4UnderLimit, ateSrc.MAC, ateSrc.IPv4, ipv4DstTraffic, "ipv4", prefixLimit-1) + configureFlow(t, topo, "IPv6.UnderLimit", srcIpv6.Name(), r6UnderLimit, ateSrc.MAC, ateSrc.IPv6, ipv6DstTraffic, "ipv6", prefixLimit-1) + configureFlow(t, topo, "IPv4.AtLimit", srcIpv4.Name(), r4AtLimit, ateSrc.MAC, ateSrc.IPv4, ipv4DstTraffic, "ipv4", prefixLimit) + configureFlow(t, topo, "IPv6.AtLimit", srcIpv6.Name(), r6AtLimit, ateSrc.MAC, ateSrc.IPv6, ipv6DstTraffic, "ipv6", prefixLimit) + configureFlow(t, topo, "IPv4.OverLimit", srcIpv4.Name(), r4OverLimit, ateSrc.MAC, ateSrc.IPv4, ipv4DstTraffic, "ipv4", prefixLimit+1) + configureFlow(t, topo, "IPv6.OverLimit", srcIpv6.Name(), r6OverLimit, ateSrc.MAC, ateSrc.IPv6, ipv6DstTraffic, "ipv6", prefixLimit+1) t.Logf("Pushing config to ATE and starting protocols...") otg.PushConfig(t, topo) @@ -229,7 +231,8 @@ func configureBGPv6Routes(peer gosnappi.BgpV6Peer, ipv6 string, name string, pre SetCount(count) } -func configureFlow(topo gosnappi.Config, name, flowSrcEndPoint, flowDstEndPoint, srcMac, srcIp, dstIp, iptype string, routeCount uint32) { +func configureFlow(t *testing.T, topo gosnappi.Config, name, flowSrcEndPoint, flowDstEndPoint, srcMac, srcIp, dstIp, iptype string, routeCount uint32) { + dut := ondatra.DUT(t, "dut") flow := topo.Flows().Add().SetName(name) flow.Metrics().SetEnable(true) flow.TxRx().Device(). @@ -237,6 +240,10 @@ func configureFlow(topo gosnappi.Config, name, flowSrcEndPoint, flowDstEndPoint, SetRxNames([]string{flowDstEndPoint}) flow.Size().SetFixed(1500) flow.Duration().FixedPackets().SetPackets(1000) + if slices.Contains(kneDeviceModelList, dut.Model()) { + flow.Size().SetFixed(1000) + flow.Rate().SetPps(750) + } e := flow.Packet().Add().Ethernet() e.Src().SetValue(srcMac) if iptype == "ipv4" { diff --git a/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/README.md b/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/README.md new file mode 100644 index 00000000000..486c611e7a0 --- /dev/null +++ b/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/README.md @@ -0,0 +1,299 @@ +# TE-6.3: Route Leakage between Non Default VRF + +## Summary + +Validate that Route leakage from VRF-1 to VRF-2 is possible and is able to +. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Topology: + +```mermaid +graph LR; +A[ATE PORT1] <--IPv4-IPv6--> B[DUT:PORT1]; +C[ATE:PORT2] <--IPv4-IPv6--> D[DUT:PORT2]; +``` + +## Procedure + +### Initial setup + +* Connect ATE port-1 to DUT port-1, and ATE port-2 to DUT port-2. +* Configure 192.1.1.1/30 to DUT port-1 and 192.1.1.2/30 to ATE port-1 and + Configure 192.1.1.4/30 to DUT port-2 and 192.1.1.5/30 to ATE port-2 +* Create a non-default VRF (VRF-1) that includes DUT port-1 and create + another non-default VRF (VRF-2) that includes DUT port-2. +* Configure eBGP from ATE port-1 (AS 65001) to DUT port-1 (AS 65003) and also + from ATE port-2 (AS 65003) to DUT port-2 (AS 65002) +* Advertise 50.1.1.0/24 route from ATE-port1 using the eBGP session. Advertise + 60.1.1.0/24 route from ATE-port2 using the eBGP session. +* Configure a bi-directional traffic flow with following parameters + Src: 50.1.1.0/24 & Dst: 60.1.1.0/24 and vice-versa + Traffic: PPS of 10,000 packets per second, Frame size of 256 bytes + +### TE-6.3.1 + +* Start the protocols from ATE-port1 and ATE-port2 +* Verify that 50.1.1.0/24 is installed in VRF-1 routing-instance and + 60.1.1.0/24 is installed in VRF-2 routing-instance. +* Start the traffic from ATE-port1 and ATE-port2 +* Validate that there is 100% traffic loss seen as the VRF-1 routes are + reachable by VRF-2 routes and vice-versa. + +### TE-6.3.2 +* Leak the route 50.1.1.0/24 from VRF-1 routing-instance of ATE-port1 to + VRF-2 routing-instance. +* Leak the route 60.1.1.0/24 from VRF-2 routing-instance of ATE-port2 to + VRF-1 routing-instance. +* Start the protocols from ATE-port1 and ATE-port2 +* Verify that 50.1.1.0/24 is installed in VRF-1 routing-instance and + 60.1.1.0/24 is installed in VRF-2 routing-instance. +* Start the traffic from ATE-port1 and ATE-port2 +* Validate that traffic is flowing in both directions with 0% traffic loss. + +## Canonical OC +```json + { + "network-instances": { + "openconfig-network-instance:network-instance": [ + { + "config": { + "name": "VRF-1", + "type": "openconfig-network-instance-types:L3VRF" + }, + "name": "VRF-1", + "protocols": { + "protocol": [ + { + "bgp": { + "global": { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "as": 65003, + "router-id": "192.1.1.1" + } + }, + "neighbors": { + "neighbor": [ + { + "afi-safis": { + "afi-safi": [ + { + "add-paths": { + "config": { + "receive": true, + "send": true + } + }, + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "enabled": true + }, + "graceful-restart": {}, + "ipv4-unicast": { + "config": { + "send-default-route": true + }, + "prefix-limit": {}, + "prefix-limit-received": {} + } + }, + { + "add-paths": { + "config": { + "receive": true, + "send": true + } + }, + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", + "enabled": true + }, + "graceful-restart": {}, + "ipv6-unicast": { + "config": { + "send-default-route": true + }, + "prefix-limit": {}, + "prefix-limit-received": {} + } + } + ] + }, + "apply-policy": { + "config": { + "default-export-policy": "ACCEPT_ROUTE", + "default-import-policy": "ACCEPT_ROUTE" + } + }, + "config": { + "neighbor-address": "192.1.1.2", + "peer-as": 65001 + }, + "ebgp-multihop": { + "config": { + "multihop-ttl": 0 + } + }, + "neighbor-address": "192.1.1.2" + } + ] + } + }, + "config": { + "identifier": "openconfig-policy-types:BGP", + "name": "BGP" + }, + "identifier": "openconfig-policy-types:BGP", + "name": "BGP" + } + ] + } + }, + { + "config": { + "name": "VRF-2", + "type": "openconfig-network-instance-types:L3VRF" + }, + "name": "VRF-2", + "protocols": { + "protocol": [ + { + "bgp": { + "global": { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "enabled": true + } + } + ] + }, + "config": { + "as": 65002, + "router-id": "192.1.1.5" + } + }, + "neighbors": { + "neighbor": [ + { + "afi-safis": { + "afi-safi": [ + { + "add-paths": { + "config": { + "receive": true, + "send": true + } + }, + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", + "enabled": true + }, + "graceful-restart": {}, + "ipv4-unicast": { + "config": { + "send-default-route": true + }, + "prefix-limit": {}, + "prefix-limit-received": {} + } + }, + { + "add-paths": { + "config": { + "receive": true, + "send": true + } + }, + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", + "config": { + "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", + "enabled": true + }, + "graceful-restart": {}, + "ipv6-unicast": { + "config": { + "send-default-route": true + }, + "prefix-limit": {}, + "prefix-limit-received": {} + } + } + ] + }, + "apply-policy": { + "config": { + "default-export-policy": "ACCEPT_ROUTE", + "default-import-policy": "ACCEPT_ROUTE" + } + }, + "config": { + "neighbor-address": "192.1.1.6", + "peer-as": 65003 + }, + "ebgp-multihop": { + "config": { + "multihop-ttl": 0 + } + }, + "neighbor-address": "192.1.1.6" + } + ] + } + }, + "config": { + "identifier": "openconfig-policy-types:BGP", + "name": "BGP" + }, + "identifier": "openconfig-policy-types:BGP", + "name": "BGP" + } + ] + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: + +paths: + /network-instances/network-instance/config/name: + /network-instances/network-instance/config/description: + + /network-instances/network-instance/state/name: + /network-instances/network-instance/state/description: +``` + +## Minimum DUT platform requirement + +FFF + diff --git a/feature/isis/otg_tests/graceful_restart_restarting/metadata.textproto b/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/metadata.textproto similarity index 52% rename from feature/isis/otg_tests/graceful_restart_restarting/metadata.textproto rename to feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/metadata.textproto index 330cb133b91..1c7647b4b81 100644 --- a/feature/isis/otg_tests/graceful_restart_restarting/metadata.textproto +++ b/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/metadata.textproto @@ -1,9 +1,8 @@ # proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto # proto-message: Metadata - -uuid: "3A0314F5-B8BF-418C-AF23-F052B3E1F28F" -plan_id: "RT-2-17" -description: "IS-IS Graceful Restart Restarting" +uuid: "c09345b2-bddf-4e5c-b4c4-22424e8cb602" +plan_id: "TE-6.3" +description: "Route Leakage between Non Default VRF" testbed: TESTBED_DUT_ATE_2LINKS platform_exceptions: { platform: { @@ -12,9 +11,7 @@ platform_exceptions: { deviations: { interface_enabled: true default_network_instance: "default" - omit_l2_mtu: true - isis_interface_afi_unsupported: true - isis_instance_enabled_required: true - missing_value_for_defaults: true + network_instance_import_export_policy_oc_unsuppored: true } -} \ No newline at end of file +} +tags: TAGS_AGGREGATION diff --git a/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/route_leakage_between_nondefault_vrfs_test.go b/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/route_leakage_between_nondefault_vrfs_test.go new file mode 100644 index 00000000000..10c373ab4e8 --- /dev/null +++ b/feature/bgp/route_selection/otg_tests/route_leakage_between_nondefault_vrfs/route_leakage_between_nondefault_vrfs_test.go @@ -0,0 +1,485 @@ +package route_leakage_between_nondefault_vrfs_test + +import ( + "fmt" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + vrf1Name = "VRF-1" + vrf2Name = "VRF-2" + dutAS1 = 65003 + dutAS2 = 65002 + ateAS1 = 65001 + ateAS2 = 65003 + globalAS = 65000 + advertisedNet1StartIP = "50.1.1.0" + advertisedNet2StartIP = "60.1.1.0" + routePrefix = 24 + routeCount = 1 + pps = 10000 + frameSize = 256 + trafficDuration = 5 * time.Second + trafficTimeout = 10 * time.Second + bgpWaitTime = 1 * time.Minute + vrf1RT = "65003:100" + vrf2RT = "65002:200" + IPV4 = "IPv4" +) + +var ( + totalPackets = pps * uint64(trafficDuration.Seconds()) + advertisedNet1 = fmt.Sprintf("%s/%d", advertisedNet1StartIP, routePrefix) + advertisedNet2 = fmt.Sprintf("%s/%d", advertisedNet2StartIP, routePrefix) + + dutPort1 = attrs.Attributes{ + Name: "port1", + Desc: "Dut port 1", + IPv4: "192.1.1.1", + IPv4Len: 30, + IPv6: "2001:DB8::1", + IPv6Len: 126, + } + + dutPort2 = attrs.Attributes{ + Name: "port2", + Desc: "Dut port 2", + IPv4: "192.1.1.5", + IPv4Len: 30, + IPv6: "2001:DB8::5", + IPv6Len: 126, + } + + otgPort1 = attrs.Attributes{ + Desc: "Otg port 1", + Name: "port1", + MAC: "00:01:12:00:00:01", + IPv4: "192.1.1.2", + IPv4Len: 30, + IPv6: "2001:DB8::2", + IPv6Len: 126, + } + + otgPort2 = attrs.Attributes{ + Desc: "Otg port 2", + Name: "port2", + MAC: "00:01:12:00:00:02", + IPv4: "192.1.1.6", + IPv4Len: 30, + IPv6: "2001:DB8::6", + IPv6Len: 126, + } +) + +type testCase struct { + name string + expectTrafficPass bool + expectLeakedRoutes bool +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestRouteLeakageBetweenNonDefaultVRFs(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + tc1 := testCase{ + name: "TE-6.3.1", + expectTrafficPass: false, + expectLeakedRoutes: false, + } + tc2 := testCase{ + name: "TE-6.3.2", + expectTrafficPass: true, + expectLeakedRoutes: true, + } + enableDefaultVRFBGP(t, dut) + configureDUT(t, dut) + config := configureATE(t, ate) + + t.Run(tc1.name, func(t *testing.T) { + runTest(t, tc1, dut, ate, config) + }) + configureRouteLeaking(t, dut) + + t.Run(tc2.name, func(t *testing.T) { + runTest(t, tc2, dut, ate, config) + }) +} + +func waitForTraffic(t *testing.T, otg *otg.OTG, flowName string, timeout time.Duration) { + transmitPath := gnmi.OTG().Flow(flowName).Transmit().State() + _, ok := gnmi.Watch(t, otg, transmitPath, timeout, func(val *ygnmi.Value[bool]) bool { + transmitState, present := val.Val() + return present && !transmitState + }).Await(t) + + if !ok { + t.Errorf("Traffic for flow %s did not stop within the timeout of %d", flowName, timeout) + } else { + t.Logf("Traffic for flow %s has stopped", flowName) + } +} + +func runTest(t *testing.T, tc testCase, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, config gosnappi.Config) { + otg := ate.OTG() + otg.PushConfig(t, config) + otg.StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), config, IPV4) + verifyBGP(t, dut) + verifyRoutes(t, dut) + verifyLeakedRoutes(t, dut, tc.expectLeakedRoutes) + + t.Logf("Starting traffic") + otg.StartTraffic(t) + for _, flow := range config.Flows().Items() { + waitForTraffic(t, otg, flow.Name(), trafficTimeout) + } + + otgutils.LogFlowMetrics(t, otg, config) + + verifyTrafficFlow(t, ate, config, tc.expectTrafficPass) +} + +func configureRouteLeaking(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.NetworkInstanceImportExportPolicyOCUnsupported(dut) { + t.Logf("Configuring route leaking through CLI") + configureRouteLeakingFromCLI(t, dut) + } else { + t.Logf("Configuring route leaking through OC") + configureRouteLeakingFromOC(t, dut) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + + t.Logf("Enabling default VRF BGP") + enableDefaultVRFBGP(t, dut) + + t.Logf("Configuring VRFs") + configureVrf(t, dut, "VRF-1", dp1.Name(), dutPort1.IPv4, otgPort1.IPv4, dutAS1, ateAS1) + configureVrf(t, dut, "VRF-2", dp2.Name(), dutPort2.IPv4, otgPort2.IPv4, dutAS2, ateAS2) + + t.Logf("Configuring Interfaces") + configureDUTPort(t, dut, &dutPort1, dp1) + configureDUTPort(t, dut, &dutPort2, dp2) +} + +func configureDUTPort(t *testing.T, dut *ondatra.DUTDevice, attrs *attrs.Attributes, p *ondatra.Port) { + t.Helper() + d := gnmi.OC() + i := attrs.NewOCInterface(p.Name(), dut) + i.Description = ygot.String(attrs.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + + i.GetOrCreateEthernet() + i4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + i4.Enabled = ygot.Bool(true) + a := i4.GetOrCreateAddress(attrs.IPv4) + a.PrefixLength = ygot.Uint8(attrs.IPv4Len) + + i6 := i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + i6.Enabled = ygot.Bool(true) + a6 := i6.GetOrCreateAddress(attrs.IPv6) + a6.PrefixLength = ygot.Uint8(attrs.IPv6Len) + + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), i) + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p) + } +} + +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + + d1 := otgPort1.AddToOTG(config, p1, &dutPort1) + d2 := otgPort2.AddToOTG(config, p2, &dutPort2) + + ip1 := d1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + ip2 := d2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + + bgp1 := d1.Bgp().SetRouterId(otgPort1.IPv4) + bgp1Peer := bgp1.Ipv4Interfaces().Add().SetIpv4Name(ip1.Name()).Peers().Add().SetName(fmt.Sprintf("%s.BGP.peer", d1.Name())) + bgp1Peer.SetPeerAddress(dutPort1.IPv4).SetAsNumber(uint32(ateAS1)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + bgp1Net := bgp1Peer.V4Routes().Add().SetName("vrf1-routes") + bgp1Net.SetNextHopIpv4Address(ip1.Address()) + bgp1Net.Addresses().Add().SetAddress(advertisedNet1StartIP).SetPrefix(24).SetCount(routeCount) + + bgp2 := d2.Bgp().SetRouterId(otgPort2.IPv4) + bgp2Peer := bgp2.Ipv4Interfaces().Add().SetIpv4Name(ip2.Name()).Peers().Add().SetName(fmt.Sprintf("%s.BGP.peer", d2.Name())) + bgp2Peer.SetPeerAddress(dutPort2.IPv4).SetAsNumber(uint32(ateAS2)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + bgp2Net := bgp2Peer.V4Routes().Add().SetName("vrf2-routes") + bgp2Net.SetNextHopIpv4Address(ip2.Address()) + bgp2Net.Addresses().Add().SetAddress(advertisedNet2StartIP).SetPrefix(routePrefix).SetCount(routeCount) + + flow1 := config.Flows().Add().SetName("VRF1_to_VRF2") + flow1.TxRx().Device().SetTxNames([]string{fmt.Sprintf("%s.IPv4", d1.Name())}).SetRxNames([]string{fmt.Sprintf("%s.IPv4", d2.Name())}) + flow1.Metrics().SetEnable(true) + flow1.Rate().SetPps(pps) + flow1.Size().SetFixed(frameSize) + flow1.Duration().SetFixedPackets(gosnappi.NewFlowFixedPackets().SetPackets(uint32(totalPackets))) + + flow1.Packet().Add().Ethernet() + v4_1 := flow1.Packet().Add().Ipv4() + v4_1.Src().SetValue(advertisedNet1StartIP) + v4_1.Dst().SetValue(advertisedNet2StartIP) + + flow2 := config.Flows().Add().SetName("VRF2_to_VRF1") + flow2.TxRx().Device().SetTxNames([]string{fmt.Sprintf("%s.IPv4", d2.Name())}).SetRxNames([]string{fmt.Sprintf("%s.IPv4", d1.Name())}) + flow2.Metrics().SetEnable(true) + flow2.Rate().SetPps(pps) + flow2.Size().SetFixed(frameSize) + flow2.Duration().SetFixedPackets(gosnappi.NewFlowFixedPackets().SetPackets(uint32(totalPackets))) + + flow2.Packet().Add().Ethernet() + v4_2 := flow2.Packet().Add().Ipv4() + v4_2.Src().SetValue(advertisedNet2StartIP) + v4_2.Dst().SetValue(advertisedNet1StartIP) + + return config +} + +func verifyBGP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + bgpPath1 := gnmi.OC().NetworkInstance(vrf1Name).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + bgpPath2 := gnmi.OC().NetworkInstance(vrf2Name).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + t.Logf("Waiting for BGP session to be ESTABLISHED in %s", vrf1Name) + status1, ok := gnmi.Watch(t, dut, bgpPath1.Neighbor(otgPort1.IPv4).SessionState().State(), bgpWaitTime, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, present := val.Val() + return present && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP VRF-1 state", bgpPath1.Neighbor(otgPort1.IPv4).State(), gnmi.Get(t, dut, bgpPath1.Neighbor(otgPort1.IPv4).State())) + t.Fatalf("BGP in %s did not establish: %v", vrf1Name, status1) + } + + t.Logf("Waiting for BGP session to be ESTABLISHED in %s", vrf2Name) + status2, ok := gnmi.Watch(t, dut, bgpPath2.Neighbor(otgPort2.IPv4).SessionState().State(), bgpWaitTime, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, present := val.Val() + return present && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGP VRF-2 state", bgpPath2.Neighbor(otgPort2.IPv4).State(), gnmi.Get(t, dut, bgpPath2.Neighbor(otgPort2.IPv4).State())) + t.Fatalf("BGP in %s did not establish: %v", vrf2Name, status2) + } +} + +// verifyRoutes checks that the advertised routes are installed in the correct VRF AFT. +func verifyRoutes(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + aftVrf1 := gnmi.OC().NetworkInstance(vrf1Name).Afts().Ipv4Entry(advertisedNet1) + _, ok := gnmi.Watch(t, dut, aftVrf1.State(), 15*time.Second, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + return val.IsPresent() + }).Await(t) + if !ok { + t.Errorf("Route %s is not installed in AFT for %s", advertisedNet1, vrf1Name) + } else { + t.Logf("Route %s successfully installed in AFT for %s", advertisedNet1, vrf1Name) + } + + aftVrf2 := gnmi.OC().NetworkInstance(vrf2Name).Afts().Ipv4Entry(advertisedNet2) + _, ok = gnmi.Watch(t, dut, aftVrf2.State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + return val.IsPresent() + }).Await(t) + if !ok { + t.Errorf("Route %s is not installed in AFT for %s", advertisedNet2, vrf2Name) + } else { + t.Logf("Route %s successfully installed in AFT for %s", advertisedNet2, vrf2Name) + } +} + +func configureRouteLeakingFromCLI(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + cli := ` + route-map RM-ALL-ROUTES permit 10 + router general + vrf VRF-1 + leak routes source-vrf VRF-2 subscribe-policy RM-ALL-ROUTES + vrf VRF-2 + leak routes source-vrf VRF-1 subscribe-policy RM-ALL-ROUTES + ` + helpers.GnmiCLIConfig(t, dut, cli) +} + +func configureRouteLeakingFromOC(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + root := &oc.Root{} + + ni1 := root.GetOrCreateNetworkInstance(vrf1Name) + ni1Pol := ni1.GetOrCreateInterInstancePolicies() + iexp1 := ni1Pol.GetOrCreateImportExportPolicy() + iexp1.SetImportRouteTarget([]oc.NetworkInstance_InterInstancePolicies_ImportExportPolicy_ImportRouteTarget_Union{oc.UnionString(vrf2RT)}) + iexp1.SetExportRouteTarget([]oc.NetworkInstance_InterInstancePolicies_ImportExportPolicy_ExportRouteTarget_Union{oc.UnionString(vrf1RT)}) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf1Name).InterInstancePolicies().Config(), ni1Pol) + + ni2 := root.GetOrCreateNetworkInstance(vrf2Name) + ni2Pol := ni2.GetOrCreateInterInstancePolicies() + iexp2 := ni2Pol.GetOrCreateImportExportPolicy() + iexp2.SetImportRouteTarget([]oc.NetworkInstance_InterInstancePolicies_ImportExportPolicy_ImportRouteTarget_Union{oc.UnionString(vrf1RT)}) + iexp2.SetExportRouteTarget([]oc.NetworkInstance_InterInstancePolicies_ImportExportPolicy_ExportRouteTarget_Union{oc.UnionString(vrf2RT)}) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrf2Name).InterInstancePolicies().Config(), ni2Pol) +} + +func verifyLeakedRoutes(t *testing.T, dut *ondatra.DUTDevice, expectLeakedRoutes bool) { + t.Helper() + t.Logf("Verifying leaked route %s in %s", advertisedNet1, vrf2Name) + aftVrf2 := gnmi.OC().NetworkInstance(vrf2Name).Afts().Ipv4Entry(advertisedNet1) + _, ok := gnmi.Watch(t, dut, aftVrf2.State(), 15*time.Second, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + return val.IsPresent() + }).Await(t) + if !ok { + if expectLeakedRoutes { + t.Errorf("Route %s was not leaked into %s unexpectedly", advertisedNet1, vrf2Name) + } else { + t.Logf("Route %s was not leaked into %s as expected", advertisedNet1, vrf2Name) + } + } else { + if expectLeakedRoutes { + t.Logf("Route %s was successfully leaked into %s as expected", advertisedNet1, vrf2Name) + } else { + t.Errorf("Route %s was leaked into %s unexpectedly", advertisedNet1, vrf2Name) + } + } + + t.Logf("Verifying leaked route %s in %s", advertisedNet2, vrf1Name) + aftVrf1 := gnmi.OC().NetworkInstance(vrf1Name).Afts().Ipv4Entry(advertisedNet2) + _, ok = gnmi.Watch(t, dut, aftVrf1.State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + return val.IsPresent() + }).Await(t) + if !ok { + if expectLeakedRoutes { + t.Errorf("Route %s was not leaked into %s unexpectedly", advertisedNet2, vrf1Name) + } else { + t.Logf("Route %s was not leaked into %s as expected", advertisedNet2, vrf1Name) + } + } else { + if expectLeakedRoutes { + t.Logf("Route %s was successfully leaked into %s as expected", advertisedNet2, vrf1Name) + } else { + t.Errorf("Route %s was leaked into %s unexpectedly", advertisedNet2, vrf1Name) + } + } +} + +func verifyTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, expectTrafficPass bool) { + t.Helper() + otg := ate.OTG() + + for _, flow := range config.Flows().Items() { + flowMetrics := gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).State()) + txPackets := flowMetrics.GetCounters().GetOutPkts() + rxPackets := flowMetrics.GetCounters().GetInPkts() + + if txPackets == 0 { + t.Fatalf("Flow %s did not send any packets", flow.Name()) + } + + if txPackets < totalPackets { + t.Errorf("Flow %s sent fewer packets than expected: sent %d, expected %d", flow.Name(), txPackets, totalPackets) + } + + var expectedPackets uint64 + if expectTrafficPass { + expectedPackets = totalPackets + } else { + expectedPackets = 0 + } + + t.Logf("Expecting %d packets for flow %s", expectedPackets, flow.Name()) + msg := fmt.Sprintf("Sent %d packets, expected %d packets, received %d packets.", txPackets, expectedPackets, rxPackets) + if rxPackets == expectedPackets { + t.Log(msg) + } else { + t.Error(msg) + } + } +} + +func enableDefaultVRFBGP(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + bgp := &oc.NetworkInstance_Protocol{ + Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + Name: ygot.String("BGP"), + Enabled: ygot.Bool(true), + Bgp: &oc.NetworkInstance_Protocol_Bgp{}, + } + bgp.Bgp.Global = &oc.NetworkInstance_Protocol_Bgp_Global{ + As: ygot.Uint32(globalAS), + RouterId: ygot.String("1.1.1.1"), + } + gnmi.Replace(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), bgp) +} + +func configureVrf(t *testing.T, dut *ondatra.DUTDevice, vrfName, interfaceName, routerId, peerAddress string, routerAS, peerAS uint32) { + t.Helper() + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(vrfName) + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + niIntf := ni.GetOrCreateInterface(interfaceName) + niIntf.Interface = ygot.String(interfaceName) + niIntf.Subinterface = ygot.Uint32(0) + + proto := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := proto.GetOrCreateBgp() + global := bgp.GetOrCreateGlobal() + global.As = ygot.Uint32(routerAS) + global.RouterId = ygot.String(routerId) + + neighbor := bgp.GetOrCreateNeighbor(peerAddress) + neighbor.PeerAs = ygot.Uint32(peerAS) + neighbor.Enabled = ygot.Bool(true) + neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + + afiSafi := global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afiSafi.Enabled = ygot.Bool(true) + + neighbor.Enabled = ygot.Bool(true) + neighbor.SendCommunityType = []oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_NONE} + + nAfiSafi := neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + nAfiSafi.Enabled = ygot.Bool(true) + nAfiSafi.GetOrCreateAddPaths().Receive = ygot.Bool(true) + nAfiSafi.GetOrCreateAddPaths().Send = ygot.Bool(true) + nAfiSafi.GetOrCreateIpv6Unicast().SendDefaultRoute = ygot.Bool(true) + + nAfiSafi4 := neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + nAfiSafi4.Enabled = ygot.Bool(true) + nAfiSafi4.GetOrCreateAddPaths().Receive = ygot.Bool(true) + nAfiSafi4.GetOrCreateAddPaths().Send = ygot.Bool(true) + nAfiSafi4.GetOrCreateIpv4Unicast().SendDefaultRoute = ygot.Bool(true) + + neighbor.GetOrCreateApplyPolicy().DefaultExportPolicy = oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE + neighbor.GetOrCreateApplyPolicy().DefaultImportPolicy = oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE + + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrfName).Config(), ni) +} diff --git a/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md index e663f722f25..4f6e168dbe3 100644 --- a/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/README.md @@ -637,3 +637,9 @@ rpcs: ## Required DUT platform * FFF + + +## Canonical OC +```json +{} +``` diff --git a/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/metadata.textproto b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/metadata.textproto index 9e9dbe86deb..d0047127223 100644 --- a/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/metadata.textproto +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/metadata.textproto @@ -29,7 +29,9 @@ platform_exceptions: { static_protocol_name: "STATIC" same_policy_attached_to_all_afis: true skip_bgp_send_community_type: true + default_bgp_instance_name: "BGP" skip_setting_disable_metric_propagation: true + default_no_igp_metric_propagation: true } } platform_exceptions: { @@ -45,6 +47,8 @@ platform_exceptions: { tc_attribute_propagation_unsupported: true tc_subscription_unsupported: true default_bgp_instance_name: "default" + bgp_set_med_action_unsupported: true + skip_bgp_peer_group_send_community_type: true } } platform_exceptions: { @@ -53,8 +57,9 @@ platform_exceptions: { } deviations: { skip_bgp_send_community_type: true - skip_setting_disable_metric_propagation: true routing_policy_tag_set_embedded: true bgp_community_set_refs_unsupported: true + bgp_set_med_action_unsupported: true + bgp_as_path_prepend_order_mismtach: true } } diff --git a/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/static_route_bgp_redistribution_test.go b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/static_route_bgp_redistribution_test.go index e1c1e601ba6..2b22c76df15 100644 --- a/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/static_route_bgp_redistribution_test.go +++ b/feature/bgp/static_route_bgp_redistribution/otg_tests/static_route_bgp_redistribution_test/static_route_bgp_redistribution_test.go @@ -24,6 +24,7 @@ package static_route_bgp_redistribution_test import ( + "context" "fmt" "reflect" "sort" @@ -36,6 +37,7 @@ import ( "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/otgutils" + gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -208,9 +210,12 @@ func configureDUTStatic(t *testing.T, dut *ondatra.DUTDevice) { func configureDUTBGP(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() - dutOcRoot := &oc.Root{} - bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, deviations.DefaultBgpInstanceName(dut)) + + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + if deviations.DefaultBgpInstanceName(dut) != "" { + bgpPath = gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, deviations.DefaultBgpInstanceName(dut)) + } // permit all policy rp := dutOcRoot.GetOrCreateRoutingPolicy() @@ -224,8 +229,13 @@ func configureDUTBGP(t *testing.T, dut *ondatra.DUTDevice) { // setup BGP networkInstance := dutOcRoot.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) - networkInstanceProtocolBgp := networkInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, deviations.DefaultBgpInstanceName(dut)) + networkInstanceProtocolBgp := networkInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") networkInstanceProtocolBgp.SetEnabled(true) + + if deviations.DefaultBgpInstanceName(dut) != "" { + networkInstanceProtocolBgp = networkInstance.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, deviations.DefaultBgpInstanceName(dut)) + networkInstanceProtocolBgp.SetEnabled(true) + } bgp := networkInstanceProtocolBgp.GetOrCreateBgp() bgpGlobal := bgp.GetOrCreateGlobal() @@ -246,6 +256,12 @@ func configureDUTBGP(t *testing.T, dut *ondatra.DUTDevice) { bgpPeerGroup := bgp.GetOrCreatePeerGroup(peerGroupName) bgpPeerGroup.SetPeerAs(dutAsn) + if deviations.SkipBgpSendCommunityType(dut) { + if !deviations.SkipBgpPeerGroupSendCommunityType(dut) { + bgpPeerGroup.SetSendCommunityType([]oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_STANDARD}) + } + } + // dutPort1 -> atePort1 peer (ebgp session) ateEBGPNeighborOne := bgp.GetOrCreateNeighbor(atePort1.IPv4) ateEBGPNeighborOne.PeerGroup = ygot.String(peerGroupName) @@ -308,11 +324,19 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { func awaitBGPEstablished(t *testing.T, dut *ondatra.DUTDevice, neighbors []string) { for _, neighbor := range neighbors { - gnmi.Await(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)). - Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, deviations.DefaultBgpInstanceName(dut)). - Bgp(). - Neighbor(neighbor). - SessionState().State(), time.Second*240, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + if deviations.DefaultBgpInstanceName(dut) == "" { + gnmi.Await(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)). + Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP"). + Bgp(). + Neighbor(neighbor). + SessionState().State(), time.Second*240, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } else { + gnmi.Await(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)). + Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, deviations.DefaultBgpInstanceName(dut)). + Bgp(). + Neighbor(neighbor). + SessionState().State(), time.Second*240, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + } } } @@ -365,7 +389,7 @@ func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { } // Configure OTG traffic-flow -func configureTrafficFlow(t *testing.T, otgConfig gosnappi.Config, isV4 bool, name, flowSrcEndPoint, flowDstEndPoint, srcMac, srcIp, dstIp string) gosnappi.Config { +func configureTrafficFlow(t *testing.T, otgConfig gosnappi.Config, isV4 bool, name, flowSrcEndPoint, flowDstEndPoint, srcMac, srcIP, dstIP string) gosnappi.Config { t.Helper() // ATE Traffic Configuration. @@ -384,12 +408,12 @@ func configureTrafficFlow(t *testing.T, otgConfig gosnappi.Config, isV4 bool, na e.Src().SetValue(srcMac) if isV4 { v4 := flow.Packet().Add().Ipv4() - v4.Src().SetValue(srcIp) - v4.Dst().SetValue(dstIp) + v4.Src().SetValue(srcIP) + v4.Dst().SetValue(dstIP) } else { v6 := flow.Packet().Add().Ipv6() - v6.Src().SetValue(srcIp) - v6.Dst().SetValue(dstIp) + v6.Src().SetValue(srcIP) + v6.Dst().SetValue(dstIP) } return otgConfig @@ -437,6 +461,15 @@ func configureTableConnection(t *testing.T, dut *ondatra.DUTDevice, isV4, mPropa addressFamily = oc.Types_ADDRESS_FAMILY_IPV6 } + if importPolicy == "" { + if deviations.DefaultNoIgpMetricPropagation(dut) { + importPolicy = redistributeStaticPolicyNameV4 + if !isV4 { + importPolicy = redistributeStaticPolicyNameV6 + } + } + } + batchSet := &gnmi.SetBatch{} tc := networkInstance.GetOrCreateTableConnection( oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, @@ -453,8 +486,10 @@ func configureTableConnection(t *testing.T, dut *ondatra.DUTDevice, isV4, mPropa if deviations.EnableTableConnections(dut) { fptest.ConfigEnableTbNative(t, dut) + gnmi.BatchUpdate(batchSet, niPath.TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, addressFamily).Config(), tc) + } else { + gnmi.BatchReplace(batchSet, niPath.TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, addressFamily).Config(), tc) } - gnmi.BatchUpdate(batchSet, niPath.TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, addressFamily).Config(), tc) if deviations.SamePolicyAttachedToAllAfis(dut) { if addressFamily == oc.Types_ADDRESS_FAMILY_IPV4 { @@ -475,7 +510,7 @@ func configureTableConnection(t *testing.T, dut *ondatra.DUTDevice, isV4, mPropa if !deviations.SkipSettingDisableMetricPropagation(dut) { tc1.SetDisableMetricPropagation(!mPropagation) } - gnmi.BatchUpdate(batchSet, niPath.TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, addressFamily).Config(), tc1) + gnmi.BatchReplace(batchSet, niPath.TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, addressFamily).Config(), tc1) } batchSet.Set(t, dut) @@ -493,6 +528,98 @@ func configureTableConnection(t *testing.T, dut *ondatra.DUTDevice, isV4, mPropa cfgplugins.DeviationCiscoTableConnectionsStatictoBGPMetricPropagation(t, dut, isV4, med, importPolicy) } } + + if mPropagation { + if deviations.DefaultNoIgpMetricPropagation(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(importPolicy) + policyDef := dutOcRoot.GetOrCreateRoutingPolicy().GetOrCreatePolicyDefinition(importPolicy) + + oldPolicyDef := gnmi.Get[*oc.RoutingPolicy_PolicyDefinition](t, dut, gnmi.OC().RoutingPolicy().PolicyDefinition(importPolicy).Config()) + statementMap := oldPolicyDef.GetOrCreateStatementMap() + + // Inject the "propagate-metric" and copy over all other statement config + // which is the only way to insert an object in an user ordered list + // keyed by name. + hasPropagateStatement := false + propagateName := "propagate-metric" + for i, statement := range statementMap.Values() { + // If we have "propagate-metric" we can skip this whole step + // because otherwise this would get the "set-med" leaf with + // the augmented value and the ygot model will drop it since + // it doesn't understand "METRIC". + if i == 0 && *statement.Name == propagateName { + hasPropagateStatement = true + break + } else { + _, err := policyDef.AppendNewStatement(propagateName) + if err != nil { + t.Fatalf("failed creating new policy statement, err: %s", err) + } + } + t.Logf("STATEMENT: %v", *statement.Name) + err := policyDef.AppendStatement(statement) + if err != nil { + t.Fatalf("failed creating append policy statement, err: %s", err) + } + } + + if !hasPropagateStatement { + // Only replace if it doesn't have the "propagate-metric" statement + gnmi.Replace(t, dut, policyPath.Config(), policyDef) + + // Inject the METRIC deviated enum value into "set-med" leaf + // and SET into "set-med-action" leaf + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{{ + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "routing-policy"}, + {Name: "policy-definitions"}, + {Name: "policy-definition", Key: map[string]string{"name": importPolicy}}, + {Name: "statements"}, + {Name: "statement", Key: map[string]string{"name": propagateName}}, + {Name: "actions"}, + {Name: "bgp-actions"}, + {Name: "config"}, + {Name: "set-med"}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_StringVal{ + StringVal: "METRIC", + }, + }, + }, { + Path: &gpb.Path{ + Elem: []*gpb.PathElem{ + {Name: "routing-policy"}, + {Name: "policy-definitions"}, + {Name: "policy-definition", Key: map[string]string{"name": importPolicy}}, + {Name: "statements"}, + {Name: "statement", Key: map[string]string{"name": propagateName}}, + {Name: "actions"}, + {Name: "bgp-actions"}, + {Name: "config"}, + {Name: "set-med-action"}, + }, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_StringVal{ + StringVal: "SET", + }, + }, + }}, + } + gnmiClient := dut.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Unexpected error set-med: %v", err) + } + } + } + } + } } // Validate configurations for table-connections and routing-policy @@ -820,8 +947,9 @@ func redistributeStaticRoutePolicyWithMED(t *testing.T, dut *ondatra.DUTDevice, policyStatementAction := policyStatement.GetOrCreateActions() policyStatementAction.SetPolicyResult(oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE) policyStatement.GetOrCreateActions().GetOrCreateBgpActions().SetSetMed(oc.UnionUint32(medValue)) - policyStatement.GetOrCreateActions().GetOrCreateBgpActions().SetSetMedAction(oc.BgpPolicy_BgpSetMedAction_SET) - + if !deviations.BGPSetMedActionUnsupported(dut) { + policyStatement.GetOrCreateActions().GetOrCreateBgpActions().SetSetMedAction(oc.BgpPolicy_BgpSetMedAction_SET) + } gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) configureTableConnection(t, dut, isV4, metricPropagate, redistributeStaticPolicyName, oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE) @@ -866,12 +994,6 @@ func redistributeStaticRoutePolicyWithLocalPreference(t *testing.T, dut *ondatra // 1.27.8 and 1.27.19 setup function func redistributeStaticRoutePolicyWithCommunitySet(t *testing.T, dut *ondatra.DUTDevice, isV4 bool) { - // Remove import policy. - tableConnV4Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV4) - gnmi.Delete(t, dut, tableConnV4Path.ImportPolicy().Config()) - tableConnV6Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV6) - gnmi.Delete(t, dut, tableConnV6Path.ImportPolicy().Config()) - redistributeStaticPolicyName := redistributeStaticPolicyNameV4 policyStatementName := policyStatementNameV4 communitySetName := "community-set-v4" @@ -893,6 +1015,15 @@ func redistributeStaticRoutePolicyWithCommunitySet(t *testing.T, dut *ondatra.DU communitySetPolicyDefinition := communitySet.GetOrCreateDefinedSets().GetOrCreateBgpDefinedSets().GetOrCreateCommunitySet(communitySetName) communitySetPolicyDefinition.SetCommunityMember([]oc.RoutingPolicy_DefinedSets_BgpDefinedSets_CommunitySet_CommunityMember_Union{oc.UnionString("64512:100")}) + // Delete the references import policy under table connection before replacing the policy. + if isV4 { + tableConnV4Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV4) + gnmi.Delete(t, dut, tableConnV4Path.ImportPolicy().Config()) + } else { + tableConnV6Path := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).TableConnection(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, oc.Types_ADDRESS_FAMILY_IPV6) + gnmi.Delete(t, dut, tableConnV6Path.ImportPolicy().Config()) + } + gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) gnmi.Replace(t, dut, communityPath.Config(), communitySetPolicyDefinition) @@ -906,6 +1037,7 @@ func redistributeStaticRoutePolicyWithCommunitySet(t *testing.T, dut *ondatra.DU if !deviations.BgpCommunitySetRefsUnsupported(dut) { policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetCommunity().SetOptions(oc.BgpPolicy_BgpSetCommunityOptionType_ADD) + policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetCommunity().SetMethod(oc.SetCommunity_Method_REFERENCE) policyStatementAction.GetOrCreateBgpActions().GetOrCreateSetCommunity().GetOrCreateReference().SetCommunitySetRefs([]string{communitySetName}) } gnmi.Replace(t, dut, policyPath.Config(), redistributePolicyDefinition) @@ -971,13 +1103,13 @@ func redistributeNullNextHopStaticRoute(t *testing.T, dut *ondatra.DUTDevice, at tagValue := "40" policyStatementName := policyStatementNameV4 ipRoute := "192.168.20.0/24" - routeNextHop := "192.168.1.9" + routeNextHop := "192.168.1.10" if !isV4 { redistributeStaticPolicyName = redistributeStaticPolicyNameV6 tagValue = "60" policyStatementName = policyStatementNameV6 ipRoute = "2024:db8:64:64::/64" - routeNextHop = "2001:DB8::9" + routeNextHop = "2001:DB8::A" } policyPath := gnmi.OC().RoutingPolicy().PolicyDefinition(redistributeStaticPolicyName) @@ -1093,6 +1225,10 @@ func validateRedistributeIPv6RoutePolicy(t *testing.T, dut *ondatra.DUTDevice, a func validatePrefixASN(t *testing.T, ate *ondatra.ATEDevice, isV4 bool, bgpPeerName, subnet string, wantASPath []uint32) { foundPrefix := false + dut := ondatra.DUT(t, "dut") + if deviations.BgpAsPathPrependOrderMismtach(dut) && isV4 { + wantASPath = []uint32{65499, 65499, 65499, 64512} + } if isV4 { prefixPath := gnmi.OTG().BgpPeer(bgpPeerName).UnicastIpv4PrefixAny() @@ -1298,18 +1434,19 @@ func validateRedistributeRouteWithTagSet(t *testing.T, dut *ondatra.DUTDevice, a } } - if isV4 { - if deviations.SkipSettingDisableMetricPropagation(dut) { - validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medIPv4, shouldBePresent) + subnet := "192.168.10.0" + if !isV4 { + subnet = "2024:db8:128:128::" + } + + if deviations.SkipSettingDisableMetricPropagation(dut) { + if deviations.DefaultNoIgpMetricPropagation(dut) { + validateLearnedPrefix(t, ate, atePort1.Name, subnet, medZero, shouldBePresent, isV4) } else { - validateLearnedIPv4Prefix(t, ate, atePort1.Name+".BGP4.peer", "192.168.10.0", medZero, shouldBePresent) + validateLearnedPrefix(t, ate, atePort1.Name, subnet, medIPv4, shouldBePresent, isV4) } } else { - if deviations.SkipSettingDisableMetricPropagation(dut) { - validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medIPv6, shouldBePresent) - } else { - validateLearnedIPv6Prefix(t, ate, atePort1.Name+".BGP6.peer", "2024:db8:128:128::", medZero, shouldBePresent) - } + validateLearnedPrefix(t, ate, atePort1.Name, subnet, medZero, shouldBePresent, isV4) } } @@ -1319,12 +1456,12 @@ func validateRedistributeNullNextHopStaticRoute(t *testing.T, dut *ondatra.DUTDe redistributeStaticPolicyName := redistributeStaticPolicyNameV4 policyStatementName := policyStatementNameV4 addressFamily := oc.Types_ADDRESS_FAMILY_IPV4 - nextHop := "192.168.1.9" + nextHop := "192.168.1.10" if !isV4 { redistributeStaticPolicyName = redistributeStaticPolicyNameV6 policyStatementName = policyStatementNameV6 addressFamily = oc.Types_ADDRESS_FAMILY_IPV6 - nextHop = "2001:db8::9" + nextHop = "2001:db8::a" } if !deviations.TcSubscriptionUnsupported(dut) { @@ -1461,6 +1598,14 @@ func validateLearnedIPv6Prefix(t *testing.T, ate *ondatra.ATEDevice, bgpPeerName } } +func validateLearnedPrefix(t *testing.T, ate *ondatra.ATEDevice, portName, subnet string, expectedMED uint32, shouldBePresent, ipv4 bool) { + if ipv4 { + validateLearnedIPv4Prefix(t, ate, portName+".BGP4.peer", subnet, expectedMED, shouldBePresent) + } else { + validateLearnedIPv6Prefix(t, ate, portName+".BGP6.peer", subnet, expectedMED, shouldBePresent) + } +} + func TestBGPStaticRouteRedistribution(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") diff --git a/feature/bgp/tests/local_bgp_test/README.md b/feature/bgp/tests/local_bgp_test/README.md index 459ee4a4d50..af03271d45a 100644 --- a/feature/bgp/tests/local_bgp_test/README.md +++ b/feature/bgp/tests/local_bgp_test/README.md @@ -10,9 +10,104 @@ The local\_bgp\_test brings up two OpenConfig controlled devices and tests that Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. - This test is suitable for running in a KNE environment. +## Canonical OpenConfig +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "global": { + "config": { + "as": 64500, + "router-id": "192.0.2.21" + } + }, + "neighbors": { + "neighbor": [ + { + "config": { + "auth-password": "password", + "local-as": 64500, + "neighbor-address": "192.168.10.2", + "peer-as": 64501 + }, + "neighbor-address": "192.168.10.2", + "timers": { + "config": { + "hold-time": 10, + "keepalive-interval": 30 + } + } + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST" + } + } + ] + }, + "apply-policy": { + "config": { + "export-policy": [ + "ALLOW" + ], + "import-policy": [ + "ALLOW" + ] + } + }, + "config": { + "description": "Description for BGP-PEER-GROUP", + "peer-group-name": "BGP-PEER-GROUP" + }, + "peer-group-name": "BGP-PEER-GROUP" + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "bgp" + }, + "identifier": "BGP", + "name": "bgp" + } + ] + } + } + ] + } +} +``` + ## OpenConfig Path and RPC Coverage ```yaml @@ -32,6 +127,14 @@ paths: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/hold-time: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/keepalive-interval: /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/config/description: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/state/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/state/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/state/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/state/export-policy: rpcs: gnmi: @@ -39,3 +142,4 @@ rpcs: gNMI.Set: ``` + diff --git a/feature/bgp/tests/local_bgp_test/local_bgp_test.go b/feature/bgp/tests/local_bgp_test/local_bgp_test.go index 3eb4f07df38..bf46f20962b 100644 --- a/feature/bgp/tests/local_bgp_test/local_bgp_test.go +++ b/feature/bgp/tests/local_bgp_test/local_bgp_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package localbgp_test +package local_bgp_test import ( "testing" @@ -58,34 +58,40 @@ const ( ateRID = "192.0.2.31" ) -func bgpWithNbr(as uint32, routerID string, nbr *oc.NetworkInstance_Protocol_Bgp_Neighbor, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { +type bgpConfig struct { + as uint32 + routerID string + applyPolicyToAfiSafi bool +} + +func bgpWithNbr(nbr *oc.NetworkInstance_Protocol_Bgp_Neighbor, dut *ondatra.DUTDevice, cfg bgpConfig) *oc.NetworkInstance_Protocol { d := &oc.Root{} ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") bgp := niProto.GetOrCreateBgp() - bgp.GetOrCreateGlobal().As = ygot.Uint32(as) - bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + bgp.GetOrCreateGlobal().SetAs(cfg.as) + bgp.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).SetEnabled(true) - if routerID != "" { - bgp.Global.RouterId = ygot.String(routerID) + if cfg.routerID != "" { + bgp.Global.SetRouterId(cfg.routerID) } // Note: we have to define the peer group even if we aren't setting any policy because it's // invalid OC for the neighbor to be part of a peer group that doesn't exist. pg := bgp.GetOrCreatePeerGroup(peerGrpName) - pg.PeerAs = ygot.Uint32(*nbr.PeerAs) + pg.SetPeerAs(*nbr.PeerAs) pg.SetDescription(descriptionPeerGrp) - if deviations.RoutePolicyUnderAFIUnsupported(dut) { - rpl := pg.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{policyName} - rpl.ExportPolicy = []string{policyName} - } else { - pgaf := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) - pgaf.Enabled = ygot.Bool(true) + pgaf := pg.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pgaf.SetEnabled(true) + if cfg.applyPolicyToAfiSafi { rpl := pgaf.GetOrCreateApplyPolicy() - rpl.ImportPolicy = []string{policyName} - rpl.ExportPolicy = []string{policyName} + rpl.SetImportPolicy([]string{policyName}) + rpl.SetExportPolicy([]string{policyName}) + } else { + rpl := pg.GetOrCreateApplyPolicy() + rpl.SetImportPolicy([]string{policyName}) + rpl.SetExportPolicy([]string{policyName}) } bgp.AppendNeighbor(nbr) @@ -160,60 +166,69 @@ func TestEstablish(t *testing.T) { ateConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() nbrPath := statePath.Neighbor(ateAttrs.IPv4) - // Remove any existing BGP config - bgpClearConfig(t, dut) - bgpClearConfig(t, ate) - // Start a new session - dutConf := bgpWithNbr(dutAS, dutRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ - PeerAs: ygot.Uint32(dutAS), - NeighborAddress: ygot.String(ateAttrs.IPv4), - PeerGroup: ygot.String(peerGrpName), - }, dut) - ateConf := bgpWithNbr(dutAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ - PeerAs: ygot.Uint32(dutAS), - NeighborAddress: ygot.String(dutAttrs.IPv4), - PeerGroup: ygot.String(peerGrpName), - }, dut) - gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) - gnmi.Replace(t, ate, ateConfPath.Config(), ateConf) - gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*180, oc.Bgp_Neighbor_SessionState_ESTABLISHED) - wantState := dutConf.Bgp - dutState := gnmi.Get(t, dut, statePath.State()) - if deviations.MissingValueForDefaults(dut) { - wantState.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).AfiSafiName = 0 - wantState.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = nil - wantState.GetOrCreateNeighbor(ateAttrs.IPv4).Enabled = nil - } - confirm.State(t, wantState, dutState) - nbr := dutState.GetNeighbor(ateAttrs.IPv4) + for _, applyPolicyToAfiSafi := range []bool{true, false} { + applyPolicyToAfiSafiStr := "false" + if applyPolicyToAfiSafi { + applyPolicyToAfiSafiStr = "true" + } + t.Run("applyPolicyToAfiSafi="+applyPolicyToAfiSafiStr, func(t *testing.T) { + // Remove any existing BGP config + bgpClearConfig(t, dut) + bgpClearConfig(t, ate) - if !nbr.GetEnabled() { - t.Errorf("Expected neighbor %v to be enabled", ateAttrs.IPv4) - } + // Start a new session + dutConf := bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ + PeerAs: ygot.Uint32(dutAS), + NeighborAddress: ygot.String(ateAttrs.IPv4), + PeerGroup: ygot.String(peerGrpName), + }, dut, bgpConfig{dutAS, dutRID, applyPolicyToAfiSafi}) + ateConf := bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ + PeerAs: ygot.Uint32(dutAS), + NeighborAddress: ygot.String(dutAttrs.IPv4), + PeerGroup: ygot.String(peerGrpName), + }, dut, bgpConfig{dutAS, ateRID, applyPolicyToAfiSafi}) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + gnmi.Replace(t, ate, ateConfPath.Config(), ateConf) + gnmi.Await(t, dut, nbrPath.SessionState().State(), time.Second*180, oc.Bgp_Neighbor_SessionState_ESTABLISHED) + wantState := dutConf.Bgp + dutState := gnmi.Get(t, dut, statePath.State()) + if deviations.MissingValueForDefaults(dut) { + wantState.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).AfiSafiName = 0 + wantState.GetOrCreateGlobal().GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = nil + wantState.GetOrCreateNeighbor(ateAttrs.IPv4).Enabled = nil + } + confirm.State(t, wantState, dutState) + nbr := dutState.GetNeighbor(ateAttrs.IPv4) - lastFlapTime := gnmi.Get(t, dut, gnmi.OC().Interface(dutPortName).LastChange().State()) - lastEstTime := gnmi.Get(t, dut, nbrPath.State()).GetLastEstablished() - if lastEstTime < lastFlapTime { - t.Errorf("Bad last-established timestamp: got %v, want >= %v", lastEstTime, lastFlapTime) - } + if !nbr.GetEnabled() { + t.Errorf("Expected neighbor %v to be enabled", ateAttrs.IPv4) + } - if got := nbr.GetEstablishedTransitions(); got != 1 { - t.Errorf("Wrong established-transitions: got %v, want 1", got) - } + lastFlapTime := gnmi.Get(t, dut, gnmi.OC().Interface(dutPortName).LastChange().State()) + lastEstTime := gnmi.Get(t, dut, nbrPath.State()).GetLastEstablished() + if lastEstTime < lastFlapTime { + t.Errorf("Bad last-established timestamp: got %v, want >= %v", lastEstTime, lastFlapTime) + } - capabilities := map[oc.E_BgpTypes_BGP_CAPABILITY]bool{ - oc.BgpTypes_BGP_CAPABILITY_ROUTE_REFRESH: false, - oc.BgpTypes_BGP_CAPABILITY_ASN32: false, - oc.BgpTypes_BGP_CAPABILITY_MPBGP: false, - } - for _, cap := range gnmi.Get(t, dut, nbrPath.SupportedCapabilities().State()) { - capabilities[cap] = true - } - for cap, present := range capabilities { - if !present { - t.Errorf("Capability not reported: %v", cap) - } + if got := nbr.GetEstablishedTransitions(); got != 1 { + t.Errorf("Wrong established-transitions: got %v, want 1", got) + } + + capabilities := map[oc.E_BgpTypes_BGP_CAPABILITY]bool{ + oc.BgpTypes_BGP_CAPABILITY_ROUTE_REFRESH: false, + oc.BgpTypes_BGP_CAPABILITY_ASN32: false, + oc.BgpTypes_BGP_CAPABILITY_MPBGP: false, + } + for _, cap := range gnmi.Get(t, dut, nbrPath.SupportedCapabilities().State()) { + capabilities[cap] = true + } + for cap, present := range capabilities { + if !present { + t.Errorf("Capability not reported: %v", cap) + } + } + }) } } @@ -237,16 +252,16 @@ func TestDisconnect(t *testing.T) { bgpClearConfig(t, ate) // Apply simple config - dutConf := bgpWithNbr(dutAS, dutRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf := bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), - }, dut) - ateConf := bgpWithNbr(dutAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutRID, true}) + ateConf := bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut) + }, dut, bgpConfig{dutAS, ateRID, true}) gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) gnmi.Replace(t, ate, ateConfPath.Config(), ateConf) @@ -292,80 +307,80 @@ func TestParameters(t *testing.T) { }{ { name: "basic internal", - dutConf: bgpWithNbr(dutAS, dutRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), - }, dut), - ateConf: bgpWithNbr(dutAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutRID, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut), + }, dut, bgpConfig{dutAS, ateRID, true}), }, { name: "basic external", - dutConf: bgpWithNbr(dutAS, dutRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutRID, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut), + }, dut, bgpConfig{ateAS, ateRID, true}), }, { name: "explicit AS", - dutConf: bgpWithNbr(dutAS, dutRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ LocalAs: ygot.Uint32(100), PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutRID, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(100), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut), + }, dut, bgpConfig{ateAS, ateRID, true}), }, { name: "explicit router id", - dutConf: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutIP, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut), + }, dut, bgpConfig{ateAS, ateRID, true}), }, { name: "password", - dutConf: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), AuthPassword: ygot.String("password"), PeerGroup: ygot.String(peerGrpName), - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutIP, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), AuthPassword: ygot.String("password"), PeerGroup: ygot.String(peerGrpName), - }, dut), - wantState: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{ateAS, ateRID, true}), + wantState: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), - }, dut), + }, dut, bgpConfig{dutAS, dutIP, true}), }, { name: "hold-time", - dutConf: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), @@ -373,41 +388,41 @@ func TestParameters(t *testing.T) { HoldTime: ygot.Uint16(holdTime), KeepaliveInterval: ygot.Uint16(keepAlive), }, - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutIP, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut), - wantState: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{ateAS, ateRID, true}), + wantState: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), Timers: &oc.NetworkInstance_Protocol_Bgp_Neighbor_Timers{ HoldTime: ygot.Uint16(holdTime), KeepaliveInterval: ygot.Uint16(keepAlive), }, - }, dut), + }, dut, bgpConfig{dutAS, dutIP, true}), }, { name: "connect-retry", - dutConf: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), PeerGroup: ygot.String(peerGrpName), Timers: &oc.NetworkInstance_Protocol_Bgp_Neighbor_Timers{ ConnectRetry: ygot.Uint16(100), }, - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutIP, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), NeighborAddress: ygot.String(dutIP), PeerGroup: ygot.String(peerGrpName), - }, dut), + }, dut, bgpConfig{ateAS, ateRID, true}), skipMsg: "Not currently supported by EOS (see b/186141921)", }, { name: "hold time negotiated", - dutConf: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + dutConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), PeerGroup: ygot.String(peerGrpName), NeighborAddress: ygot.String(ateIP), @@ -415,8 +430,8 @@ func TestParameters(t *testing.T) { HoldTime: ygot.Uint16(holdTime), KeepaliveInterval: ygot.Uint16(keepAlive), }, - }, dut), - ateConf: bgpWithNbr(ateAS, ateRID, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{dutAS, dutIP, true}), + ateConf: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(dutAS), PeerGroup: ygot.String(peerGrpName), NeighborAddress: ygot.String(dutIP), @@ -424,8 +439,8 @@ func TestParameters(t *testing.T) { HoldTime: ygot.Uint16(135), KeepaliveInterval: ygot.Uint16(45), }, - }, dut), - wantState: bgpWithNbr(dutAS, dutIP, &oc.NetworkInstance_Protocol_Bgp_Neighbor{ + }, dut, bgpConfig{ateAS, ateRID, true}), + wantState: bgpWithNbr(&oc.NetworkInstance_Protocol_Bgp_Neighbor{ PeerAs: ygot.Uint32(ateAS), NeighborAddress: ygot.String(ateIP), Timers: &oc.NetworkInstance_Protocol_Bgp_Neighbor_Timers{ @@ -433,7 +448,7 @@ func TestParameters(t *testing.T) { NegotiatedHoldTime: ygot.Uint16(holdTime), KeepaliveInterval: ygot.Uint16(keepAlive), }, - }, dut), + }, dut, bgpConfig{dutAS, dutIP, true}), }, } for _, tc := range cases { diff --git a/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/README.md b/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/README.md index 9c8b4600be0..53aaf2667bb 100644 --- a/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/README.md +++ b/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/README.md @@ -6,15 +6,18 @@ BGP Keepalive and HoldTimer Configuration Test ## Procedure -1 Establish eBGP sessions as follows between ATE and DUT +1 Establish eBGP sessions as follows between ATE and DUT + * The DUT has eBGP peering with ATE port 1 and ATE port 2. * Enable an Accept-route all import-policy/export-policy under the BGP peer-group AFI/SAFI. * The first pair is called the "source" pair, and the second the "destination" pair -2 Validate BGP session state on DUT using telemetry. -3 Modify BGP timer values on iBGP peers to 10/30 and on the eBGP peering to 5/15. -4 Verify that the sessions are established after soft reset. -5 Repeat Step 3 by modifying BGP timers at peer-group level to 10/30(keepalive-interval/hold-time) and on eBGP peering to 5/15(keepalive-interval/hold-time) and verify the timers +2 Validate BGP session state on DUT using telemetry. +3 Modify BGP timer values on iBGP peers to 10/30 and 5/15 on the eBGP peering. +4 Verify that the sessions are established after soft reset. +5 Repeat Step 3 by modifying BGP timers at peer-group level to +10/30(keepalive-interval/hold-time) and on eBGP peering to +5/15(keepalive-interval/hold-time) and verify the timers ## Canonical OpenConfig for keepalive-interval and hold-time at neighbour and peer-group level for BGP @@ -35,6 +38,7 @@ BGP Keepalive and HoldTimer Configuration Test "neighbor": [ { "config": { + "description": "Description for neigbor 192.0.2.6", "neighbor-address": "192.0.2.6" }, "neighbor-address": "192.0.2.6", @@ -87,12 +91,14 @@ OC paths used for test setup are not listed here. ```yaml paths: ## Config Paths ## + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/description: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/keepalive-interval: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/config/hold-time: /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/keepalive-interval: /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/hold-time: ## State Paths ## + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/description: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/keepalive-interval: /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/timers/state/hold-time: /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/state/keepalive-interval: @@ -110,3 +116,4 @@ rpcs: vRX + diff --git a/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/bgp_keepalive_and_holdtimer_configuration_test.go b/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/bgp_keepalive_and_holdtimer_configuration_test.go index c672fc4ed81..f8d8dc816e9 100644 --- a/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/bgp_keepalive_and_holdtimer_configuration_test.go +++ b/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/bgp_keepalive_and_holdtimer_configuration_test.go @@ -256,6 +256,7 @@ func bgpCreateNbr(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { for _, nbr := range bgpNbrs { if nbr.isV4 { nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.SetDescription("Description for neigbor " + nbr.neighborip) nv4.PeerAs = ygot.Uint32(nbr.peerAs) nv4.Enabled = ygot.Bool(true) nv4.PeerGroup = ygot.String(bgpGlobalAttrs.peerGrpNamev4) @@ -283,6 +284,7 @@ func bgpCreateNbr(dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { } } else { nv6 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv6.SetDescription("Description for neigbor " + nbr.neighborip) nv6.PeerAs = ygot.Uint32(nbr.peerAs) nv6.Enabled = ygot.Bool(true) nv6.PeerGroup = ygot.String(bgpGlobalAttrs.peerGrpNamev6) @@ -493,6 +495,14 @@ func (tc *testCase) run(t *testing.T, conf *config, dut *ondatra.DUTDevice, ate t.Run("verifyBGPTimers", func(t *testing.T) { tc.verifyBGPTimers(t, dut) }) + // Verify BGP Neighbor Descriptions + for _, nbr := range bgpNbrs { + neighborDescription := "Description for neigbor " + nbr.neighborip + bgpPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + if description := gnmi.Get(t, dut, bgpPath.Neighbor(nbr.neighborip).Description().State()); description != neighborDescription { + t.Errorf("Neighbor description: got %v, want %v", description, neighborDescription) + } + } } func configureDUTATE(t *testing.T) (*config, *ondatra.DUTDevice, *ondatra.ATEDevice) { diff --git a/feature/cfm/otg_tests/cfm_base/README.md b/feature/cfm/otg_tests/cfm_base/README.md new file mode 100644 index 00000000000..0ffe01e8cbd --- /dev/null +++ b/feature/cfm/otg_tests/cfm_base/README.md @@ -0,0 +1,860 @@ +# CFM-1.1: CFM over ETHoCWoMPLSoGRE + +## Summary + +This test verifies CFM "DOWN MEP" can be established over "EthoCWoMPLSoGRE" dataplane. +## Testbed type + +* [`featureprofiles/topologies/atedut_8.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_8.testbed) + +## Procedure + +### Test environment setup + +```text +DUT has an ingress and 2 egress aggregate interfaces. + + [ ATE Port 1 ]----| Port1 :DUT1: Port2 | ---- |Port2 :DUT2: Port1 | ---- |ATE Port 2 | +``` + +Test uses aggregate 802.3ad bundled interfaces (Aggregate Interfaces). + +* Ingress Port: nTraffic is generated from Aggregate1 (ATE Ports 1). + +* Egress Ports: Aggregate2 (ATE Port 2) is used as the destination ports for encapsulated traffic. + +* Transit Ports: Aggregate3 (DUT1 port 2 and DUT2 Port 2) + +### CFM-1.1.1: Generate DUT Configuration + +Aggregate 1 and Aggregate2 i.e "customer interface's" are the customer facing ports that could either have port mode configuration or attachment mode configuration as described below. + +EACH test should be run twice - once with port mode configuration and once with attachment in sub-interface mode configuration. + + +port mode configuration implies CFM configured on main Port as an attachment and vlan-mode is when cfm is confirued between attachments on ansub-interface. + +Port mode will have Link loss forwarding enabled while sub-interface mode may not. + +#### Aggregate 1 "customer interface" Port mode configuration + +* Configure DUT1 port 1 to be a member of aggregate interface named "customer interface" +* "customer interface" is a static Layer 2 bundled interface part of pseudowire that accepts packets from all VLANs. +* MTU default 9216 + +#### Aggregate 1 "customer interface" Attachment mode configuration + +* Configure DUT1 port 1 to be a member of aggregate interface named "customer interface" +* Create a sub interface of the aggregated interface and assign a VLAN ID to it. +* This sub interface will be a static Layer 2 bundled interface part of pseudowire that accepts packets from vlan ID associated with it. +* MTU default 9216 + +#### Aggregate 2 "customer interface" Port mode configuration + +* Configure DUT2 port 1 to be a member of aggregate interface named "customer interface" +* "customer interface" is a static Layer 2 bundled interface part of pseudowire that accepts packets from all VLANs. +* MTU default 9216 + +#### Aggregate 2 "customer interface" Attachment mode configuration + +* Configure DUT2 port 1 to be a member of aggregate interface named "customer interface" +* Create a sub interface of the aggregated interface and assign a VLAN ID to it. +* This sub interface will be a static Layer 2 bundled interface part of pseudowire that accepts packets from vlan ID associated with it. +* MTU default 9216 + +#### Policy Forwarding Configuration + +* Policy-forwarding enabling EthoMPLSoGRE encapsulation of all incoming traffic: + + * The forwarding policy must allow forwarding of incoming traffic across 16 tunnels. 16 Tunnels has 16 source address and a single tunnel destination. + + * Source address must be configurable as: + * Loopback address OR + * 16 source addresses corresponding to a single tunnel destinations to achieve maximum entropy. + + * DSCP of the innermost IP packet header must be preserved during encapsulation + + * DSCP of the GRE/outermost IP header must be configurable (Default TOS 96) during encapsulation + + * TTL of the outer GRE must be configurable (Default TTL 64) + + * QoS Hardware queues for all traffic must be configurable (default QoS hardaware class selected is 3) + +### Pseudowire (PW) Configuration + +* "Customer interface" is Aggregate 1 and Aggregate 2 pointing towards Aggregare3 +* Two unique static MPLS label for local label and remote label. +* Enable control word + +### Aggregate 3 configuration + +* IPV4 and IPV6 addresses + +* MTU (default 9216) + +* LACP Member link configuration + +* Lag id + +* LACP (default: period short) + +* Carrier-delay (default up:3000 down:150) + +* Statistics load interval (default:30 seconds) + +### Routing + +* Create static route for tunnel destination pointing towards Aggregate 2. +* Static mapping of MPLS label for encapsulation must be configurable + +### MPLS Label + +* Entire Label block must be reallocated for static MPLS +* Labels from start/end/mid ranges must be usable and configured corresponding to EthoMPLSoGRE encapsulation + + +### CFM + +CFM is cnfigured as UP MEP. The control plane is between the customer attachments on either PF. + +### CFM-1.1.2: Verify PF CFM establishment over EthoMPLSoGRE encapsulate + +* Configure CFM session on DUT1 Port 1 and DUT2 Port 1 at line rate with a mix of both IPV4 and IPv6 traffic. Use 64, 128, 256, 512, 1024 MTU bytes frame size. +* Adjacent ATE Ports must have an Active L2. + +Verify: + +* CFM session is established with default timers and All ccm PDUs are EthoCWoMPLSoGRE-encapsulated. +* Verify deadtimer at 3.5 (100ms --> 350ms) times the configured keepalive. +* +* CCM PDUs are constant flows hence are not ECMP'd across all available tunnels. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### CFM-1.1.3: Verify PF CFM packet integrity + +* Use same configuration profile as CFM-1.1.2 (Default Timers). + +Verify: +* CCM PDU Destination is Multicast. +* CFM OpCode as - Continuity check message (1). +* Increasing sequence number for consequent CCM packets. +* Verify interval field in CCM packet for following fields. +* Transmitted interval 100ms. +* Max Lifetime 350ms. +* Min lifetime 325ms. + +#### CFM-1.1.3.1: Verify PF CFM Alarm + +* Use same configuration profile as CFM-1.1.2 +* Configure Wrong MD level on on endpoint. +* Revert MD level and configure different CCM interval (i.e 100ms and (10ms or 1s)) + +Verify: + +* “wrong MD level” alarm (when induced) on the endpoints. +* Verify “wrong interval” alarm (when induced) on the endpoints. + + +### CFM-1.1.4: Verify RDI bit set for CCM PDUs on a CE-PE fault + +* Use configuration profile as descrbied in CFM-1.1.2 +* turn off TX/Shutdown ATE port 1 (only on one endpoint). + +Verify: + +* CCM PDU is recieved on the remote endpoint with the RDI flag Set. +* A "Remote Defect Detected" alarm is raised at the remote end-point. + +### CFM-1.1.5: Verify RDI bit set for CCM PDUs on a PE-PE fault + +* Use same configuration profile as descrbied in CFM-1.1.2. +* turn off DUT 1 port 2 (only one endpoint). + +Verify: + +* Both DUT 1 and DUT 2 observe a CCM timeoute Event +* DUT 1 and DUT 2 emit a CCM PDU with the RDI flag Set. + + +#### CFM-1.1.5.1: Verify TX actuation on a remote defect or a CCM timeout + +* Use configuration profile as describedd in CFM-1.1.2 (Port mode only). +* turn off TX/Shutdown ATE port 1 (only on one endpoint). +* revert TX on ATE port 1 and turn off DUT 1 port 2 (only one endpoint). + +Verify: + +* CCM PDU is recieved on the remote endpoint with the RDI flag Set. +* A "Remote Defect Detected" alarm is raised at the remote end-point. + + +### CFM-1.1.6: Verify CFM Loss threshold can be configrued on DUT + +* Use configuration profile as describedd in CFM-1.1.2. +* re-write defulat Loss threshold (3) knob to 6, 10 , 20, 100 and 255. +* Shutdown DUT 1 port 2 (only one endpoint). + + +### CFM-1.1.8: Verify CFM Delay measurement. + +* Use configuration profile as described in CFM-1.1.2. +* Configure 2 way DM measurement profiles on CFM session with 60 second measurement intervals. + +Verify: + +* DUT 1 and DUT 2 observe a min, max and mean Delay measurements at defined. + +### CFM-1.1.9: Verify CFM synthetic loss measurement). + +* Use configuration profile as described in CFM-1.1.2. +* + +Verify: + +* Both DUT 1 and DUT 2 observe a min, max and mean frame loss ratio for "far-end" & "near-end" with 60 second measurement intervals. + +### CFM-1.1.10: Verify CFM scale. + +* Use configuration profile as describedd in CFM-1.1.2. +* Configure 1000 attachments, pseudowires and Establish 1000 CFM sessions between operating at 100ms keepalive: + +Verify: + +* Device Health and soak for at least 6 hrs. + +## Canonical OC + +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "GEO_1", + "enabled": true, + "mtu": 9080, + "name": "Bundle-Ether43", + "type": "ieee8023adLag" + }, + "name": "Bundle-Ether43", + "subinterfaces": { + "subinterface": [ + { + "config": { + "description": "GEO_1", + "enabled": true, + "index": 1600 + }, + "index": 1600, + "vlan": { + "match": { + "single-tagged": { + "config": { + "vlan-id": 1600 + } + } + } + } + } + ] + } + }, + { + "config": { + "description": "GEO_2", + "enabled": true, + "mtu": 9080, + "name": "Bundle-Ether5", + "type": "ieee8023adLag" + }, + "name": "Bundle-Ether5", + "subinterfaces": { + "subinterface": [ + { + "config": { + "description": "GEO_2", + "enabled": true, + "index": 4010 + }, + "index": 4010, + "vlan": { + "match": { + "single-tagged": { + "config": { + "vlan-id": 4010 + } + } + } + } + } + ] + } + }, + { + "config": { + "description": "GEO_3", + "enabled": true, + "mtu": 9080, + "name": "Bundle-Ether5", + "type": "ieee8023adLag" + }, + "name": "Bundle-Ether5", + "subinterfaces": { + "subinterface": [ + { + "config": { + "description": "GEO_3", + "enabled": true, + "index": 4050 + }, + "index": 4050, + "vlan": { + "match": { + "single-tagged": { + "config": { + "vlan-id": 4050 + } + } + } + } + } + ] + } + }, + { + "config": { + "description": "GEO_4", + "enabled": true, + "mtu": 9080, + "name": "Bundle-Ether43", + "type": "ieee8023adLag" + }, + "name": "Bundle-Ether43", + "subinterfaces": { + "subinterface": [ + { + "config": { + "description": "GEO_4", + "enabled": true, + "index": 1100 + }, + "index": 1100, + "vlan": { + "match": { + "single-tagged": { + "config": { + "vlan-id": 1100 + } + } + } + } + } + ] + } + } + ] + } +} +``` + + +``` json +{ + "oam": { + "cfm": { + "domains": { + "maintenance-domain": [ + { + "config": { + "char-string": "D1", + "level": 5, + "md-id": "1", + "md-name-type": "CHARACTER_STRING" + }, + "maintenance-associations": { + "maintenance-association": [ + { + "config": { + "ccm-interval": "300MS", + "group-name": "GEO_1", + "loss-threshold": 3, + "ma-id": "S1", + "ma-name-type": "UINT16", + "unsigned-int16": 1 + }, + "ma-id": "S1", + "mep-endpoints": { + "mep-endpoint": [ + { + "config": { + "ccm-enabled": true, + "direction": "UP", + "interface": "Bundle-Ether43.1600", + "local-mep-id": 40 + }, + "local-mep-id": 40, + "pm-profiles": { + "pm-profile": [ + { + "config": { + "profile-name": "cfm_delay_Bundle-Ether43_1600" + }, + "profile-name": "cfm_delay_Bundle-Ether43_1600" + }, + { + "config": { + "profile-name": "cfm_loss_Bundle-Ether43_1600" + }, + "profile-name": "cfm_loss_Bundle-Ether43_1600" + } + ] + }, + "rdi": { + "config": { + "transmit-on-defect": true + } + }, + "remote-meps": { + "remote-mep": [ + { + "config": { + "id": 39 + }, + "id": 39 + } + ] + } + } + ] + } + } + ] + }, + "md-id": "1" + }, + { + "config": { + "char-string": "D2", + "level": 5, + "md-id": "2", + "md-name-type": "CHARACTER_STRING" + }, + "maintenance-associations": { + "maintenance-association": [ + { + "config": { + "ccm-interval": "300MS", + "group-name": "GEO_2", + "loss-threshold": 3, + "ma-id": "S1", + "ma-name-type": "UINT16", + "unsigned-int16": 1 + }, + "ma-id": "S1", + "mep-endpoints": { + "mep-endpoint": [ + { + "config": { + "ccm-enabled": true, + "direction": "UP", + "interface": "Bundle-Ether5.4010", + "local-mep-id": 6 + }, + "local-mep-id": 6, + "pm-profiles": { + "pm-profile": [ + { + "config": { + "profile-name": "cfm_delay_Bundle-Ether5_4010" + }, + "profile-name": "cfm_delay_Bundle-Ether5_4010" + }, + { + "config": { + "profile-name": "cfm_loss_Bundle-Ether5_4010" + }, + "profile-name": "cfm_loss_Bundle-Ether5_4010" + } + ] + }, + "rdi": { + "config": { + "transmit-on-defect": true + } + }, + "remote-meps": { + "remote-mep": [ + { + "config": { + "id": 5 + }, + "id": 5 + } + ] + } + } + ] + } + } + ] + }, + "md-id": "2" + }, + { + "config": { + "char-string": "D3", + "level": 5, + "md-id": "3", + "md-name-type": "CHARACTER_STRING" + }, + "maintenance-associations": { + "maintenance-association": [ + { + "config": { + "ccm-interval": "300MS", + "group-name": "GEO_3", + "loss-threshold": 3, + "ma-id": "S1", + "ma-name-type": "UINT16", + "unsigned-int16": 1 + }, + "ma-id": "S1", + "mep-endpoints": { + "mep-endpoint": [ + { + "config": { + "ccm-enabled": true, + "direction": "UP", + "interface": "Bundle-Ether5.4050", + "local-mep-id": 8 + }, + "local-mep-id": 8, + "pm-profiles": { + "pm-profile": [ + { + "config": { + "profile-name": "cfm_delay_Bundle-Ether5_4050" + }, + "profile-name": "cfm_delay_Bundle-Ether5_4050" + }, + { + "config": { + "profile-name": "cfm_loss_Bundle-Ether5_4050" + }, + "profile-name": "cfm_loss_Bundle-Ether5_4050" + } + ] + }, + "rdi": { + "config": { + "transmit-on-defect": true + } + }, + "remote-meps": { + "remote-mep": [ + { + "config": { + "id": 7 + }, + "id": 7 + } + ] + } + } + ] + } + } + ] + }, + "md-id": "3" + }, + { + "config": { + "char-string": "D4", + "level": 5, + "md-id": "4", + "md-name-type": "CHARACTER_STRING" + }, + "maintenance-associations": { + "maintenance-association": [ + { + "config": { + "ccm-interval": "300MS", + "group-name": "GEO_4", + "loss-threshold": 3, + "ma-id": "S1", + "ma-name-type": "UINT16", + "unsigned-int16": 1 + }, + "ma-id": "S1", + "mep-endpoints": { + "mep-endpoint": [ + { + "config": { + "ccm-enabled": true, + "direction": "UP", + "interface": "Bundle-Ether43.1100", + "local-mep-id": 36 + }, + "local-mep-id": 36, + "pm-profiles": { + "pm-profile": [ + { + "config": { + "profile-name": "cfm_delay_Bundle-Ether43_1100" + }, + "profile-name": "cfm_delay_Bundle-Ether43_1100" + }, + { + "config": { + "profile-name": "cfm_loss_Bundle-Ether43_1100" + }, + "profile-name": "cfm_loss_Bundle-Ether43_1100" + } + ] + }, + "rdi": { + "config": { + "transmit-on-defect": true + } + }, + "remote-meps": { + "remote-mep": [ + { + "config": { + "id": 35 + }, + "id": 35 + } + ] + } + } + ] + } + } + ] + }, + "md-id": "4" + } + ] + }, + "performance-measurement-profiles-global": { + "performance-measurement-profile": [ + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "DMM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_delay_Bundle-Ether43_1100", + "repetition-period": 1 + }, + "profile-name": "cfm_delay_Bundle-Ether43_1100" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "DMM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_delay_Bundle-Ether43_1600", + "repetition-period": 1 + }, + "profile-name": "cfm_delay_Bundle-Ether43_1600" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "DMM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_delay_Bundle-Ether5_4010", + "repetition-period": 1 + }, + "profile-name": "cfm_delay_Bundle-Ether5_4010" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "DMM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_delay_Bundle-Ether5_4050", + "repetition-period": 1 + }, + "profile-name": "cfm_delay_Bundle-Ether5_4050" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "SLM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_loss_Bundle-Ether43_1100", + "repetition-period": 1 + }, + "profile-name": "cfm_loss_Bundle-Ether43_1100" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "SLM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_loss_Bundle-Ether43_1600", + "repetition-period": 1 + }, + "profile-name": "cfm_loss_Bundle-Ether43_1600" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "SLM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_loss_Bundle-Ether5_4010", + "repetition-period": 1 + }, + "profile-name": "cfm_loss_Bundle-Ether5_4010" + }, + { + "config": { + "burst-interval": 10000, + "intervals-archived": 5, + "measurement-interval": 1, + "measurement-type": "SLM", + "packet-per-burst": 100, + "packets-per-meaurement-period": 60, + "profile-name": "cfm_loss_Bundle-Ether5_4050", + "repetition-period": 1 + }, + "profile-name": "cfm_loss_Bundle-Ether5_4050" + } + ] + } + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # interface configs + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + /interfaces/interface/config/mtu: + /interfaces/interface/config/name: + /interfaces/interface/config/type: + /interfaces/interface/rates/config/load-interval: + /interfaces/interface/subinterfaces/subinterface/config/description: + /interfaces/interface/subinterfaces/subinterface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/config/index: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/mtu: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/mtu: + /interfaces/interface/aggregation/config/lag-type: + + #psuedowire configs + /network-instances/network-instance/config/name: + /network-instances/network-instance/config/type: + /network-instances/network-instance/connection-points/connection-point/config/connection-point-id: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/config/endpoint-id: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/interface: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/subinterface: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/remote/config/virtual-circuit-identifier: + + #TODO: Add new OCs for labels and next-hop-group under connection-point + #/network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/local-label + #/network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/remote-label + #/network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/remote/config/next-hop-group + + + #Tunnels/Next-hop group configs + #TODO: Revisit and update as per https://github.com/openconfig/public/pull/1308 + #network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/config/index: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/config/next-hop: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/config/index: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/type: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/dst-ip: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/src-ip: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/dscp: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/ip-ttl: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/index: + + + # Telemetry paths + + /oam/cfm/domains/maintenance-domain/state/md-id: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/state/ma-id: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/state/local-mep-id: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/remote-meps/remote-mep/state/id: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/state/profile-name: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/delay-measurement-state/frame-delay-two-way-average: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/delay-measurement-state/frame-delay-two-way-max: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/delay-measurement-state/frame-delay-two-way-min: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/measurement-type: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/loss-measurement-state/far-end-average-frame-loss-ratio: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/loss-measurement-state/far-end-max-frame-loss-ratio: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/loss-measurement-state/far-end-min-frame-loss-ratio: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/loss-measurement-state/near-end-average-frame-loss-ratio: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/loss-measurement-state/near-end-max-frame-loss-ratio: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/state/loss-measurement-state/near-end-min-frame-loss-ratio: + + + # Config paths for GRE decap + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-gre: + + # Config Paths for CFM + #/oam/cfm/domains/domain/config/name: + /oam/cfm/domains/maintenance-domain/config/level: + /oam/cfm/domains/maintenance-domain/config/md-id: + /oam/cfm/domains/maintenance-domain/config/md-name-type: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/config/ccm-interval: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/config/loss-threshold: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/config/ma-id: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/config/ma-name-type: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/config/ccm-enabled: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/config/direction: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/config/interface: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/config/local-mep-id: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/pm-profiles/pm-profile/config/profile-name: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/rdi/config/transmit-on-defect: + /oam/cfm/domains/maintenance-domain/maintenance-associations/maintenance-association/mep-endpoints/mep-endpoint/remote-meps/remote-mep/config/id: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/burst-interval: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/intervals-archived: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/measurement-interval: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/measurement-type: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/packet-per-burst: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/packets-per-meaurement-period: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/profile-name: + /oam/cfm/performance-measurement-profiles-global/performance-measurement-profile/config/repetition-period: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true + +``` + +## Required DUT platform +* MFF +* FFF \ No newline at end of file diff --git a/feature/cfm/otg_tests/cfm_base/metadata.textproto b/feature/cfm/otg_tests/cfm_base/metadata.textproto new file mode 100644 index 00000000000..f31e3184ea4 --- /dev/null +++ b/feature/cfm/otg_tests/cfm_base/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "101ebaf7-467a-48f9-865b-2b255ea49c9a" +plan_id: "CFM-1.1" +description: "CFM over ETHoCWoMPLSoGRE" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/container/containerz/tests/container_lifecycle/containerz_test.go b/feature/container/containerz/tests/container_lifecycle/containerz_test.go index cb4e0153f9f..e50405ee324 100644 --- a/feature/container/containerz/tests/container_lifecycle/containerz_test.go +++ b/feature/container/containerz/tests/container_lifecycle/containerz_test.go @@ -5,12 +5,11 @@ import ( "flag" "fmt" "os" + "path/filepath" "strings" "testing" "time" - "path/filepath" - "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -40,7 +39,6 @@ func TestMain(m *testing.M) { } func containerzClient(ctx context.Context, t *testing.T) *client.Client { - dut := ondatra.DUT(t, "dut") switch dut.Vendor() { case ondatra.ARISTA: @@ -55,6 +53,8 @@ func containerzClient(ctx context.Context, t *testing.T) *client.Client { ! `).Append(t) } + case ondatra.NOKIA: + break default: t.Fatalf("dut %s does not support containerz", dut.Name()) } @@ -134,7 +134,7 @@ func deployAndStartContainer(ctx context.Context, t *testing.T, cli *client.Clie // 2. Optionally remove existing image before push. if opts.RemoveExistingImage { t.Logf("Attempting to remove existing image %s:%s before push.", opts.ImageName, opts.ImageTag) - if err := cli.RemoveImage(ctx, opts.ImageName, opts.ImageTag, false); err != nil { + if err := cli.RemoveImage(ctx, opts.ImageName, opts.ImageTag, true); err != nil { s, _ := status.FromError(err) if s.Code() != codes.NotFound && err.Error() != client.ErrNotFound.Error() { t.Logf("Pre-push removal of image %s:%s failed (continuing with push): %v", opts.ImageName, opts.ImageTag, err) @@ -370,7 +370,7 @@ func TestRetrieveLogs(t *testing.T) { t.Logf("Got expected error when retrieving logs for non-existent instance %s: %v", nonExistentInstanceName, err) s, _ := status.FromError(err) if s.Code() != codes.NotFound && s.Code() != codes.Unknown { - t.Errorf("Expected codes.NotFound or codes.Unknown for non-existent instance %s, but got %s.", nonExistentInstanceName, s.Code()) + t.Errorf("Expected gRPC status codes NotFound or Unknown for non-existent instance %s, but got %s.", nonExistentInstanceName, s.Code()) } if logCh != nil { t.Errorf("Expected nil logCh when cli.Logs returns an error for non-existent instance %s, but got %v", nonExistentInstanceName, logCh) @@ -401,7 +401,7 @@ func TestRetrieveLogs(t *testing.T) { t.Logf("Got expected error from log channel for non-existent instance %s: %v", nonExistentInstanceName, msg.Error) s, _ := status.FromError(msg.Error) if s.Code() != codes.NotFound && s.Code() != codes.Unknown { - t.Errorf("Expected codes.NotFound or codes.Unknown from channel for non-existent instance %s, but got %s.", nonExistentInstanceName, s.Code()) + t.Errorf("Expected gRPC status codes NotFound or Unknown from channel for non-existent instance %s, but got %s.", nonExistentInstanceName, s.Code()) } } else { // An actual log message was received, which is an error for this test case. @@ -461,7 +461,7 @@ func TestRetrieveLogs(t *testing.T) { t.Errorf("Error for stopped instance %s was not a gRPC status error: %v", stoppedInstanceName, err) } else if s.Code() != codes.NotFound && s.Code() != codes.FailedPrecondition && s.Code() != codes.Unknown { // Allow Unknown as some systems might report it this way, similar to non-existent. - t.Errorf("Expected codes.NotFound, codes.FailedPrecondition, or codes.Unknown for stopped instance %s, but got %s.", stoppedInstanceName, s.Code()) + t.Errorf("Expected gRPC status codes NotFound, FailedPrecondition, or Unknown for stopped instance %s, but got %s.", stoppedInstanceName, s.Code()) } if logCh != nil { t.Errorf("Expected nil logCh when cli.Logs returns an error for stopped instance %s, but got %v", stoppedInstanceName, logCh) @@ -484,7 +484,7 @@ func TestRetrieveLogs(t *testing.T) { if !ok { t.Errorf("Stream error for stopped instance %s was not a gRPC status error: %v", stoppedInstanceName, msg.Error) } else if s.Code() != codes.NotFound && s.Code() != codes.FailedPrecondition && s.Code() != codes.Unknown { - t.Errorf("Expected codes.NotFound, codes.FailedPrecondition, or codes.Unknown from channel for stopped instance %s, but got %s.", stoppedInstanceName, s.Code()) + t.Errorf("Expected gRPC status code NotFound, FailedPrecondition, or Unknown from channel for stopped instance %s, but got %s.", stoppedInstanceName, s.Code()) } foundErrorOnChannel = true break @@ -630,7 +630,7 @@ func TestStopContainer(t *testing.T) { t.Logf("Got expected error when stopping non-existent instance %s: %v", nonExistentInstance, err) s, _ := status.FromError(err) if s.Code() != codes.NotFound { - t.Logf("Warning: StopContainer for non-existent instance %s returned code %s, not codes.NotFound. This might be acceptable depending on server behavior.", nonExistentInstance, s.Code()) + t.Logf("Warning: StopContainer for non-existent instance %s returned gRPC status code %s, not NotFound. This might be acceptable depending on server behavior.", nonExistentInstance, s.Code()) } } }) @@ -647,8 +647,8 @@ func TestStopContainer(t *testing.T) { // Attempt to stop it again. if err := localStartedCli.StopContainer(ctx, instanceName, true); err != nil { s, _ := status.FromError(err) - if s.Code() == codes.NotFound { - t.Logf("Second StopContainer() for %s returned NotFound, which is acceptable: %v", instanceName, err) + if s.Code() == codes.NotFound || s.Code() == codes.FailedPrecondition { + t.Logf("Second StopContainer() for %s returned gRPC status code NotFound or FailedPrecondition: %v", instanceName, err) } else { t.Errorf("Second StopContainer() for already stopped instance %s failed unexpectedly: %v", instanceName, err) } @@ -756,9 +756,9 @@ func TestVolumes(t *testing.T) { // An error was returned. It should be codes.NotFound. s, ok := status.FromError(err) if !ok || s.Code() != codes.NotFound { - t.Errorf("RemoveVolume(%q) for a non-existent volume returned error %v, want a gRPC error with code NotFound", nonExistentVolumeName, err) + t.Errorf("RemoveVolume(%q) for a non-existent volume returned error %v, want gRPC status code NotFound", nonExistentVolumeName, err) } else { - t.Logf("RemoveVolume(%q) for a non-existent volume correctly returned codes.NotFound.", nonExistentVolumeName) + t.Logf("RemoveVolume(%q) for a non-existent volume correctly returned gRPC status NotFound.", nonExistentVolumeName) } } }) @@ -837,7 +837,7 @@ func TestUpgrade(t *testing.T) { // Optionally, check for specific gRPC status code, e.g., codes.NotFound s, ok := status.FromError(err) if ok && s.Code() != codes.NotFound { - t.Errorf("Expected codes.NotFound for non-existent image, got %s", s.Code()) + t.Errorf("Expected gRPC status code NotFound for non-existent image, got %s", s.Code()) } } }) @@ -855,7 +855,7 @@ func TestUpgrade(t *testing.T) { t.Logf("Got expected error when upgrading to image %s with non-existent tag %s: %v", imageName, nonExistentTag, err) s, ok := status.FromError(err) if ok && s.Code() != codes.NotFound { - t.Errorf("Expected codes.NotFound (or similar) for non-existent tag, got %s", s.Code()) + t.Errorf("Expected gRPC status code NotFound (or similar) for non-existent tag, got %s", s.Code()) } } }) @@ -874,7 +874,7 @@ func TestUpgrade(t *testing.T) { t.Logf("Got expected error when upgrading non-existent instance %s: %v", nonExistentInstance, err) s, ok := status.FromError(err) if ok && s.Code() != codes.NotFound { - t.Errorf("Expected codes.NotFound for non-existent instance, got %s", s.Code()) + t.Errorf("Expected gRPC status code NotFound for non-existent instance, got %s", s.Code()) } } }) @@ -886,7 +886,6 @@ func pushPluginImage(ctx context.Context, t *testing.T, cli *client.Client, plug t.Logf("Attempting to deploy plugin tarball %q as %s:%s", pluginTarPath, pluginName, pluginImageTag) // The 'true' argument indicates this is a plugin image. progCh, err := cli.PushImage(ctx, pluginName, pluginImageTag, pluginTarPath, true) - if err != nil { return fmt.Errorf("PushImage (for plugin %q) failed: %w", pluginName, err) } @@ -1005,7 +1004,7 @@ func TestPlugins(t *testing.T) { pluginName := "non-existent-plugin-image" pluginInstance := "test-instance-non-existent-image" dummyConfigFile := filepath.Join(t.TempDir(), "dummy_config.json") - if err := os.WriteFile(dummyConfigFile, []byte(`{"description":"dummy"}`), 0644); err != nil { + if err := os.WriteFile(dummyConfigFile, []byte(`{"description":"dummy"}`), 0o644); err != nil { t.Fatalf("Failed to write dummy config file: %v", err) } @@ -1022,8 +1021,8 @@ func TestPlugins(t *testing.T) { } else { t.Logf("Got expected error when starting with non-existent image %q: %v", pluginName, err) s, ok := status.FromError(err) - if !ok || s.Code() != codes.Unknown { - t.Errorf("Expected gRPC status codes.Unknown for non-existent image, got: %v (status code: %s)", err, s.Code()) + if !ok || (s.Code() != codes.Unknown && s.Code() != codes.FailedPrecondition) { + t.Errorf("Expected gRPC status code Unknown or NotFound for non-existent image, got: %v (status code: %s)", err, s.Code()) } } }) @@ -1066,8 +1065,8 @@ func TestPlugins(t *testing.T) { } else { t.Logf("Got expected error when starting already started instance %s: %v", pluginInstance, err) s, ok := status.FromError(err) - if !ok || s.Code() != codes.Unknown { - t.Errorf("Expected gRPC status codes.Unknown for already started instance, got: %v (status code: %s)", err, s.Code()) + if !ok || (s.Code() != codes.Unknown && s.Code() != codes.AlreadyExists) { + t.Errorf("Expected gRPC status code Unknown or AlreadyExists for already started instance, got: %v (status code: %s)", err, s.Code()) } } }) diff --git a/feature/system/gnmi/cliorigin/tests/cli_origin_test/README.md b/feature/gnmi/cliorigin/tests/cli_origin_test/README.md similarity index 89% rename from feature/system/gnmi/cliorigin/tests/cli_origin_test/README.md rename to feature/gnmi/cliorigin/tests/cli_origin_test/README.md index 1c7fb50b92f..bf341cd387e 100644 --- a/feature/system/gnmi/cliorigin/tests/cli_origin_test/README.md +++ b/feature/gnmi/cliorigin/tests/cli_origin_test/README.md @@ -37,3 +37,13 @@ the emergency case that is covered by this requirement. ## Config Parameter coverage ## Telemetry Parameter coverage + +## OpenConfig Path and RPC Coverage + +TODO: Update the Paths and RPCs used. + +```yaml +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/feature/system/gnmi/cliorigin/tests/cli_origin_test/cli_origin_test.go b/feature/gnmi/cliorigin/tests/cli_origin_test/cli_origin_test.go similarity index 98% rename from feature/system/gnmi/cliorigin/tests/cli_origin_test/cli_origin_test.go rename to feature/gnmi/cliorigin/tests/cli_origin_test/cli_origin_test.go index d9e25ce4755..c3c7c97c8e0 100644 --- a/feature/system/gnmi/cliorigin/tests/cli_origin_test/cli_origin_test.go +++ b/feature/gnmi/cliorigin/tests/cli_origin_test/cli_origin_test.go @@ -136,9 +136,7 @@ func nokiaCLI(intf string, enabled bool) string { if enabled { op = "enable" } - return fmt.Sprintf(` - /interface %s admin-state %s - `, intf, op) + return fmt.Sprintf(`/interface %s admin-state %s`, intf, op) } func juniperCLI(intf string, enabled bool) string { diff --git a/feature/system/gnmi/cliorigin/tests/cli_origin_test/metadata.textproto b/feature/gnmi/cliorigin/tests/cli_origin_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/cliorigin/tests/cli_origin_test/metadata.textproto rename to feature/gnmi/cliorigin/tests/cli_origin_test/metadata.textproto diff --git a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md b/feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md similarity index 100% rename from feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md rename to feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/README.md diff --git a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto b/feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto rename to feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/metadata.textproto diff --git a/feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go b/feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go similarity index 100% rename from feature/system/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go rename to feature/gnmi/cliorigin/tests/mixed_oc_cli_origin_support_test/mixed_oc_cli_origin_support_test.go diff --git a/feature/system/gnmi/metadata/tests/annotation_test/README.md b/feature/gnmi/metadata/tests/annotation_test/README.md similarity index 89% rename from feature/system/gnmi/metadata/tests/annotation_test/README.md rename to feature/gnmi/metadata/tests/annotation_test/README.md index 273c1c2c4ec..37564eccfa9 100644 --- a/feature/system/gnmi/metadata/tests/annotation_test/README.md +++ b/feature/gnmi/metadata/tests/annotation_test/README.md @@ -29,3 +29,13 @@ the root (i.e., a configuration that contains `openconfig-metadata:protobuf-metadata` at any level other than under the root can be rejected). The WBB device implementation can map this to an internal path to store the configuration. + +## OpenConfig Path and RPC Coverage + +TODO: Update the Paths and RPCs used. + +```yaml +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/feature/system/gnmi/metadata/tests/annotation_test/annotation_test.go b/feature/gnmi/metadata/tests/annotation_test/annotation_test.go similarity index 100% rename from feature/system/gnmi/metadata/tests/annotation_test/annotation_test.go rename to feature/gnmi/metadata/tests/annotation_test/annotation_test.go diff --git a/feature/system/gnmi/metadata/tests/annotation_test/metadata.textproto b/feature/gnmi/metadata/tests/annotation_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/metadata/tests/annotation_test/metadata.textproto rename to feature/gnmi/metadata/tests/annotation_test/metadata.textproto diff --git a/feature/system/gnmi/metadata/tests/large_set_consistency_test/README.md b/feature/gnmi/metadata/tests/large_set_consistency_test/README.md similarity index 100% rename from feature/system/gnmi/metadata/tests/large_set_consistency_test/README.md rename to feature/gnmi/metadata/tests/large_set_consistency_test/README.md diff --git a/feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go b/feature/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go similarity index 92% rename from feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go rename to feature/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go index 29157f2f33e..b3f466b23f0 100644 --- a/feature/system/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go +++ b/feature/gnmi/metadata/tests/large_set_consistency_test/large_set_consistency_test.go @@ -142,13 +142,30 @@ func extractMetadataAnnotation(t *testing.T, gnmiClient gpb.GNMIClient, dut *ond } // buildGNMISetRequest builds gnmi set request with protobuf-metadata -func buildGNMISetRequest(t *testing.T, metadataText string, baselineConfig *oc.Root) *gpb.SetRequest { +func buildGNMISetRequest(t *testing.T, metadataText string, baselineConfig *oc.Root, size int) *gpb.SetRequest { + var trimSize float64 + + // For 100KB and 1M cases trim the data according to proto and base64encoding overheads + if size >= 100000 { + randomBytes := make([]byte, size) + _, err := io.ReadFull(rand.Reader, randomBytes) + t.Logf("Length of randomBytes: %d\n", len(randomBytes)) + if err != nil { + t.Fatalf("failed to generate random bytes: %v", err) + } + // Encode the bytes to a base64 string. + // account for 4 byte proto message overhead and 25% for subsequent base64encoding overhead + trimSize = 0.75*float64(size) - 4 + largeMetadata := base64.StdEncoding.EncodeToString(randomBytes) + metadataText = largeMetadata[:int(trimSize)] + } msg := &gpb.ModelData{Name: metadataText} b, err := proto.Marshal(msg) if err != nil { t.Fatalf("cannot marshal proto msg - error: %v", err) } metadataEncoded := base64.StdEncoding.EncodeToString(b) + j := map[string]any{ "@": map[string]any{ "openconfig-metadata:protobuf-metadata": metadataEncoded, @@ -229,7 +246,7 @@ func testLargeMetadata(t *testing.T, gnmiClient gpb.GNMIClient, dut *ondatra.DUT largeMetadata := base64.StdEncoding.EncodeToString(randomBytes) largeMetadata = largeMetadata[:size] // send large metadata update request in one goroutine - gpbSetRequest := buildGNMISetRequest(t, largeMetadata, baselineConfig) + gpbSetRequest := buildGNMISetRequest(t, largeMetadata, baselineConfig, size) t.Log("gnmiClient Set large metadataconfig request") _, err = gnmiClient.Set(context.Background(), gpbSetRequest) if err != nil { @@ -259,7 +276,8 @@ func TestLargeSetConsistency(t *testing.T) { gnmiClient := dut.RawAPIs().GNMI(t) // send 1st update request in one goroutine - gpbSetRequest := buildGNMISetRequest(t, shortStringMetadata1, baselineConfig) + sizeMetadata1 := len(shortStringMetadata1) + gpbSetRequest := buildGNMISetRequest(t, shortStringMetadata1, baselineConfig, sizeMetadata1) t.Log("gnmiClient Set 1st large config") if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { t.Fatalf("gnmi.Set unexpected error: %v", err) @@ -272,7 +290,8 @@ func TestLargeSetConsistency(t *testing.T) { ch := make(chan struct{}, 1) // sending 2nd update request in one goroutine - gpbSetRequest = buildGNMISetRequest(t, shortStringMetadata2, baselineConfig) + sizeMetadata2 := len(shortStringMetadata2) + gpbSetRequest = buildGNMISetRequest(t, shortStringMetadata2, baselineConfig, sizeMetadata2) wg.Add(1) go func() { defer wg.Done() diff --git a/feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto b/feature/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto rename to feature/gnmi/metadata/tests/large_set_consistency_test/metadata.textproto diff --git a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md b/feature/gnmi/otg_tests/drained_configuration_convergence_time/README.md similarity index 100% rename from feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/README.md rename to feature/gnmi/otg_tests/drained_configuration_convergence_time/README.md diff --git a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go b/feature/gnmi/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go similarity index 99% rename from feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go rename to feature/gnmi/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go index 5db33dc8180..1dee255c2de 100644 --- a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go +++ b/feature/gnmi/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_bgp_test.go @@ -21,9 +21,9 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/openconfig/featureprofiles/feature/system/gnmi/benchmarking/internal/setup" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnmi/setup" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" diff --git a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go b/feature/gnmi/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go similarity index 98% rename from feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go rename to feature/gnmi/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go index fbdbba61792..1d787cb0c8b 100644 --- a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go +++ b/feature/gnmi/otg_tests/drained_configuration_convergence_time/drained_configuration_convergence_time_isis_test.go @@ -20,8 +20,8 @@ import ( "testing" "time" - "github.com/openconfig/featureprofiles/feature/system/gnmi/benchmarking/internal/setup" "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/gnmi/setup" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" diff --git a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/metadata.textproto b/feature/gnmi/otg_tests/drained_configuration_convergence_time/metadata.textproto similarity index 96% rename from feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/metadata.textproto rename to feature/gnmi/otg_tests/drained_configuration_convergence_time/metadata.textproto index 088184533b4..d2c770c8154 100644 --- a/feature/system/gnmi/benchmarking/otg_tests/drained_configuration_convergence_time/metadata.textproto +++ b/feature/gnmi/otg_tests/drained_configuration_convergence_time/metadata.textproto @@ -23,6 +23,7 @@ platform_exceptions: { } deviations: { isis_level_enabled: true + bgp_set_med_action_unsupported: true } } platform_exceptions: { diff --git a/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md b/feature/gnmi/tests/full_configuration_replace_test/README.md similarity index 100% rename from feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/README.md rename to feature/gnmi/tests/full_configuration_replace_test/README.md diff --git a/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go b/feature/gnmi/tests/full_configuration_replace_test/full_configuration_replace_test.go similarity index 97% rename from feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go rename to feature/gnmi/tests/full_configuration_replace_test/full_configuration_replace_test.go index 03d8878c59e..d690fdd535d 100644 --- a/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/full_configuration_replace_test.go +++ b/feature/gnmi/tests/full_configuration_replace_test/full_configuration_replace_test.go @@ -19,10 +19,10 @@ import ( "testing" "time" - "github.com/openconfig/featureprofiles/feature/system/gnmi/benchmarking/internal/setup" "github.com/openconfig/featureprofiles/internal/args" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnmi/setup" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" diff --git a/feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto b/feature/gnmi/tests/full_configuration_replace_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/benchmarking/tests/full_configuration_replace_test/metadata.textproto rename to feature/gnmi/tests/full_configuration_replace_test/metadata.textproto diff --git a/feature/gnmi/tests/gnmi_ni_test/README.md b/feature/gnmi/tests/gnmi_ni_test/README.md new file mode 100644 index 00000000000..1c29e98ba00 --- /dev/null +++ b/feature/gnmi/tests/gnmi_ni_test/README.md @@ -0,0 +1,129 @@ +# gNMI-1.6: System gRPC Servers running in more than one network-instance + +## Summary + +Ensure that a grpc server serving GNMI can be configured on a second +network-instance named "GNMI_TEST". + +## Testbed type + +* Specify the .testbed topology file from the + [topologies](https://github.com/openconfig/featureprofiles/tree/main/topologies) + folder to be used with this test + +## Procedure + +### Test environment setup + +* Use `--deviation_default_network_instance` for the name of the default +network-instance. +* Use `--deviation_gnmi_server_name` for the name of the gnmi server. + +### gNMI-1.6.1: Configure two gnmi servers on different network instances + +The DUT is expected to have a gnmi server running on some network-instance +already. Generate and push the following configuration to the +DUT to add a second gNMI server to the DUT on the GNMI_TEST network-instance. + +## Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "test interface", + "name": "port1" + }, + "name": "port1", + "subinterfaces": { + "subinterface": [ + { + "config": { + "index": 0 + }, + "index": 0, + "ipv4": { + "addresses": { + "address": [ + { + "config": { + "ip": "192.0.2.1", + "prefix-length": 32 + }, + "ip": "192.0.2.1" + } + ] + } + } + } + ] + } + } + ] + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "GNMI_TEST" + }, + "interfaces": { + "interface": [ + { + "config": { + "id": "port1", + "interface": "port1" + }, + "id": "port1" + } + ] + }, + "name": "GNMI_TEST" + } + ] + }, + "system": { + "grpc-servers": { + "grpc-server": [ + { + "config": { + "enable": true, + "name": "gmmi-test", + "network-instance": "GNMI_TEST", + "port": 9339, + "services": [ + "GNMI" + ] + }, + "name": "gmmi-test" + } + ] + } + } +} +``` + +### gNMI-1.6.2: Perform set and subscribe to each server + +* Set the DUT port1 interface description using the default GNMI connection. +* Subscribe ONCE to the interface port1 description to ensure it was changed. +* Set the DUT port1 interface description using the GNMI_TEST connection. +* Subscribe ONCE to the interface port1 using the default gnmi connection and +ensure it was changed. + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /system/grpc-servers/grpc-server/config/name: + /system/grpc-servers/grpc-server/config/enable: + /system/grpc-servers/grpc-server/config/port: + /system/grpc-servers/grpc-server/config/network-instance: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Subscribe: + +``` diff --git a/feature/gnmi/tests/gnmi_ni_test/gnmi_ni_test.go b/feature/gnmi/tests/gnmi_ni_test/gnmi_ni_test.go new file mode 100644 index 00000000000..51aa99123ca --- /dev/null +++ b/feature/gnmi/tests/gnmi_ni_test/gnmi_ni_test.go @@ -0,0 +1,136 @@ +package gnmi_ni_test + +import ( + "testing" + + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + customVRFName = "customVRFName" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + IPv4: "192.0.2.1", + IPv4Len: 30, + IPv6: "2001:0db8::192:0:2:1", + IPv6Len: 126, + Subinterface: 0, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: 30, + IPv6: "2001:0db8::192:0:2:5", + IPv6Len: 126, + Subinterface: 0, + } + dutPort1NetworkInstanceIParams = cfgplugins.NetworkInstanceParams{ + Name: "DEFAULT", + Default: true, + } + + dutPort2NetworkInstanceIParams = cfgplugins.NetworkInstanceParams{ + Name: customVRFName, + Default: false, + } +) + +func TestCodelab(t *testing.T) { + // configure DUT + dut := ondatra.DUT(t, "dut") + batch := &gnmi.SetBatch{} + ConfigureDUT(batch, t, dut) + ConfigureAdditionalNetworkInstance(batch, t, dut, customVRFName) +} + +// ConfigureDUT configures port1 and port2 on the DUT with default network instance. +func ConfigureDUT(batch *gnmi.SetBatch, t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + t.Logf("\nConfiguring network instance and gNMI server: Network instance: %s \n", dutPort1NetworkInstanceIParams.Name) + // Configure default network instance. + cfgplugins.NewNetworkInstance(t, batch, dut, &dutPort1NetworkInstanceIParams) + + // Configure gNMI server on default network instance. + cfgplugins.CreateGNMIServer(batch, t, dut, &dutPort1NetworkInstanceIParams) + + // Configuring basic interface and network instance as some devices only populate OC after configuration. + port1IntfPath := dutPort1.NewOCInterface(dp1.Name(), dut) + gnmi.BatchUpdate(batch, gnmi.OC().Interface(dp1.Name()).Config(), port1IntfPath) + // Deviations for vendors that require explicit interface to network instance assignment. + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + cfgplugins.AssignInterfaceToNetworkInstance(t, batch, dut, dp1.Name(), &dutPort1NetworkInstanceIParams, 0) + } +} + +// ConfigureAdditionalNetworkInstance configures a new network instance in DUT and changes the network instance of port2 +func ConfigureAdditionalNetworkInstance(batch *gnmi.SetBatch, t *testing.T, dut *ondatra.DUTDevice, ni string) { + // Configure interface, non-default network instance + t.Logf("\nConfiguring network instance and gNMI server: Network instance: %s \n", ni) + cfgplugins.NewNetworkInstance(t, batch, dut, &dutPort2NetworkInstanceIParams) + + // Configure non-default gnmi server. + cfgplugins.CreateGNMIServer(batch, t, dut, &dutPort2NetworkInstanceIParams) + + // Assign port2 to custom network instance for all vendors + dp2 := dut.Port(t, "port2") + port2IntfPath := dutPort2.NewOCInterface(dp2.Name(), dut) + gnmi.BatchUpdate(batch, gnmi.OC().Interface(dp2.Name()).Config(), port2IntfPath) + cfgplugins.AssignInterfaceToNetworkInstance(t, batch, dut, dp2.Name(), &dutPort2NetworkInstanceIParams, 0) + + t.Log("\nApplying configuration to DUT\n") + + batch.Set(t, dut) + + for _, netInstance := range gnmi.GetAll(t, dut, gnmi.OC().NetworkInstanceAny().State()) { + t.Logf("Network instance: %s", netInstance.GetName()) + } + + // Get and validate states for default and custom networkinstances. + gnmiServerList := gnmi.GetAll(t, dut, gnmi.OC().System().GrpcServerAny().State()) + for _, gnmiServer := range gnmiServerList { + if gnmiServer.GetName() == deviations.DefaultNetworkInstance(dut) { + defaultInstanceState := gnmi.Get(t, dut, gnmi.OC().System().GrpcServer(deviations.DefaultNetworkInstance(dut)).State()) + validateGnmiServerState(t, defaultInstanceState) + } + if gnmiServer.GetName() == customVRFName { + customInstanceState := gnmi.Get(t, dut, gnmi.OC().System().GrpcServer(customVRFName).State()) + validateGnmiServerState(t, customInstanceState) + } + } + + // Two Servers should be running on the DUT. + gnmiServerCount := len(gnmi.GetAll(t, dut, gnmi.OC().System().GrpcServerAny().State())) + + // Two gNMI servers or more are expected. + if gnmiServerCount < 2 { + t.Fatalf("Expected 2+ gNMI servers, got %d.", gnmiServerCount) + } +} + +// validateGnmiServerState checks and logs the state of a gNMI server. +func validateGnmiServerState(t *testing.T, state *oc.System_GrpcServer) { + if state == nil { + t.Errorf("gNMI server state is nil") + return + } + if !state.GetEnable() { + t.Errorf("Expected gNMI server '%s' to be enabled, but it is not.", state.GetName()) + } + t.Logf("gNMI Server: %s, running on network instance: %s, listening port: %v, Enabled: %t", + state.GetName(), state.GetNetworkInstance(), state.GetPort(), state.GetEnable()) +} diff --git a/feature/gnmi/tests/gnmi_ni_test/metadata.textproto b/feature/gnmi/tests/gnmi_ni_test/metadata.textproto new file mode 100644 index 00000000000..459d599e613 --- /dev/null +++ b/feature/gnmi/tests/gnmi_ni_test/metadata.textproto @@ -0,0 +1,41 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "bc288f94-cb04-4bb0-b1d7-621cfaa7e9f4" +plan_id: "gNMI-1.6" +description: "System gRPC Servers running in more than one network-instance" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + explicit_interface_in_default_vrf: true + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + interface_enabled: true + } +} diff --git a/feature/system/gnmi/set/tests/gnmi_set_test/README.md b/feature/gnmi/tests/gnmi_set_test/README.md similarity index 100% rename from feature/system/gnmi/set/tests/gnmi_set_test/README.md rename to feature/gnmi/tests/gnmi_set_test/README.md diff --git a/feature/system/gnmi/set/tests/gnmi_set_test/gnmi_set_test.go b/feature/gnmi/tests/gnmi_set_test/gnmi_set_test.go similarity index 100% rename from feature/system/gnmi/set/tests/gnmi_set_test/gnmi_set_test.go rename to feature/gnmi/tests/gnmi_set_test/gnmi_set_test.go diff --git a/feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto b/feature/gnmi/tests/gnmi_set_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/set/tests/gnmi_set_test/metadata.textproto rename to feature/gnmi/tests/gnmi_set_test/metadata.textproto diff --git a/feature/gnmi/tests/leaflist_update_test/README.md b/feature/gnmi/tests/leaflist_update_test/README.md new file mode 100644 index 00000000000..a8e9ad19ef1 --- /dev/null +++ b/feature/gnmi/tests/leaflist_update_test/README.md @@ -0,0 +1,49 @@ +# gNMI-1.24: gNMI Leaf-List Update Test + +## Description + +This test validates the behavior of gNMI `Replace` and `Update` operations on a leaf-list field in the OpenConfig model. The test specifically targets the `/system/dns/config/search` path. + +The test follows these steps: + +1. **Initial Replace**: It uses a gNMI `Replace` operation to set the value of `/system/dns/config/search` to `["google.com"]`. +2. **Validation**: It verifies that the DUT successfully updated the path and that the new value is `["google.com"]`. +3. **Update**: It then uses a gNMI `Update` operation to add `"youtube.com"` to the existing leaf-list. +4. **Final Validation**: It verifies that the leaf-list at `/system/dns/state/search` now contains both `"google.com"` and `"youtube.com"`. + +The test will fail if the values at any stage do not match the expected results. + +## Canonical OC + +```json +{ + "system": { + "dns": { + "config": { + "search": [ + "google.com", + "youtube.com" + ] + } + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + /system/dns/config/search: + /system/dns/state/search: +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Required DUT platform + +vRX - virtual router device diff --git a/feature/gnmi/tests/leaflist_update_test/leaflist_update_test.go b/feature/gnmi/tests/leaflist_update_test/leaflist_update_test.go new file mode 100644 index 00000000000..50f5a54cc0e --- /dev/null +++ b/feature/gnmi/tests/leaflist_update_test/leaflist_update_test.go @@ -0,0 +1,62 @@ +// Copyright 2025 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 +// +// https://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 leaflist_update_test + +import ( + "sort" + "testing" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestLeafListUpdate(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + // Configure the DNS search list to ["google.com"] using Replace. + dnsConfig := &oc.System_Dns{} + dnsConfig.Search = []string{"google.com"} + gnmi.Replace(t, dut, gnmi.OC().System().Dns().Config(), dnsConfig) + + // Verify the DNS search list is ["google.com"]. + searchList := gnmi.Get[[]string](t, dut, gnmi.OC().System().Dns().Search().State()) + if len(searchList) != 1 || searchList[0] != "google.com" { + t.Fatalf("Expected search list to be [\"google.com\"], but got %v", searchList) + } + + // Update the DNS search list to add "youtube.com". + gnmi.Update(t, dut, gnmi.OC().System().Dns().Config(), &oc.System_Dns{ + Search: []string{"youtube.com"}, + }) + + // Verify the DNS search list now contains both "google.com" and "youtube.com". + finalSearchList := gnmi.Get[[]string](t, dut, gnmi.OC().System().Dns().Search().State()) + sort.Strings(finalSearchList) + expectedList := []string{"google.com", "youtube.com"} + if len(finalSearchList) != len(expectedList) { + t.Fatalf("Expected search list to be %v, but got %v", expectedList, finalSearchList) + } + for i := range finalSearchList { + if finalSearchList[i] != expectedList[i] { + t.Fatalf("Expected search list to be %v, but got %v", expectedList, finalSearchList) + } + } +} diff --git a/feature/gnmi/tests/leaflist_update_test/metadata.textproto b/feature/gnmi/tests/leaflist_update_test/metadata.textproto new file mode 100644 index 00000000000..bb76332a397 --- /dev/null +++ b/feature/gnmi/tests/leaflist_update_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ded8e821-3503-4a1b-a675-2f4ba117c5ee" +plan_id: "gNMI-1.24" +description: "gNMI Leaf-List Update Test" +testbed: TESTBED_DUT diff --git a/feature/gnmi/tests/system_gnmi_get_test/README.md b/feature/gnmi/tests/system_gnmi_get_test/README.md new file mode 100644 index 00000000000..758ebd2d414 --- /dev/null +++ b/feature/gnmi/tests/system_gnmi_get_test/README.md @@ -0,0 +1,13 @@ +# gNMI-1.9: Get requests + +TODO(robshakir): fill in test plan from code already written. + +## OpenConfig Path and RPC Coverage + +TODO: Update the Paths and RPCs used. + +```yaml +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/feature/system/gnmi/get/tests/system_gnmi_get_test/metadata.textproto b/feature/gnmi/tests/system_gnmi_get_test/metadata.textproto similarity index 100% rename from feature/system/gnmi/get/tests/system_gnmi_get_test/metadata.textproto rename to feature/gnmi/tests/system_gnmi_get_test/metadata.textproto diff --git a/feature/system/gnmi/get/tests/system_gnmi_get_test/system_gnmi_get_test.go b/feature/gnmi/tests/system_gnmi_get_test/system_gnmi_get_test.go similarity index 100% rename from feature/system/gnmi/get/tests/system_gnmi_get_test/system_gnmi_get_test.go rename to feature/gnmi/tests/system_gnmi_get_test/system_gnmi_get_test.go diff --git a/feature/gnoi/bootconfig/feature.textproto b/feature/gnoi/bootconfig/feature.textproto deleted file mode 100644 index fe12039b65e..00000000000 --- a/feature/gnoi/bootconfig/feature.textproto +++ /dev/null @@ -1,30 +0,0 @@ -# Copyright 2025 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 -# -# https://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. -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_bootconfig" - version: 1 -} - -gnoi_service { - service_name: "gnoi.bootconfig.BootConfig" - method_name: "SetBootConfig" -} - -gnoi_service { - service_name: "gnoi.bootconfig.BootConfig" - method_name: "GetBootConfig" -} \ No newline at end of file diff --git a/feature/gnoi/factory_reset/feature.textproto b/feature/gnoi/factory_reset/feature.textproto deleted file mode 100644 index eaa711ccaa3..00000000000 --- a/feature/gnoi/factory_reset/feature.textproto +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_factory_reset" - version: 1 -} - -gnoi_service { - service_name: "gnoi.factory_reset.FactoryReset" - method_name: "Start" -} diff --git a/feature/gnoi/factory_reset/tests/factory_reset_test/factory_reset_test.go b/feature/gnoi/factory_reset/tests/factory_reset_test/factory_reset_test.go index c26a148955b..a0760232660 100644 --- a/feature/gnoi/factory_reset/tests/factory_reset_test/factory_reset_test.go +++ b/feature/gnoi/factory_reset/tests/factory_reset_test/factory_reset_test.go @@ -16,8 +16,8 @@ package factory_reset_test import ( "context" - "crypto/md5" "crypto/rand" + "crypto/sha256" "io" "path/filepath" "regexp" @@ -27,7 +27,7 @@ import ( "github.com/openconfig/featureprofiles/internal/fptest" frpb "github.com/openconfig/gnoi/factory_reset" fpb "github.com/openconfig/gnoi/file" - "github.com/openconfig/gnoi/types" + tpb "github.com/openconfig/gnoi/types" "github.com/openconfig/gnoigo" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" @@ -81,7 +81,7 @@ func gNOIPutFile(t *testing.T, dut *ondatra.DUTDevice, gnoiClient gnoigo.Clients t.Fatalf("Failed to create stream channel: %v", err) } defer stream.CloseSend() - h := md5.New() + h := sha256.New() fPutOpen := &fpb.PutRequest_Open{ Open: &fpb.PutRequest_Details{ RemoteFile: fullPath, @@ -113,8 +113,8 @@ func gNOIPutFile(t *testing.T, dut *ondatra.DUTDevice, gnoiClient gnoigo.Clients hashReq := &fpb.PutRequest{ Request: &fpb.PutRequest_Hash{ - Hash: &types.HashType{ - Method: types.HashType_MD5, + Hash: &tpb.HashType{ + Method: tpb.HashType_SHA256, Hash: h.Sum(nil), }, }, diff --git a/feature/gnoi/file/feature.textproto b/feature/gnoi/file/feature.textproto deleted file mode 100644 index fda97967dee..00000000000 --- a/feature/gnoi/file/feature.textproto +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_file" - version: 1 -} - -gnoi_service { - service_name: "gnoi.file.File" - method_name: "Get" -} -gnoi_service { - service_name: "gnoi.file.File" - method_name: "TransferToRemote" -} -gnoi_service { - service_name: "gnoi.file.File" - method_name: "Put" -} -gnoi_service { - service_name: "gnoi.file.File" - method_name: "Stat" -} -gnoi_service { - service_name: "gnoi.file.File" - method_name: "Remove" -} diff --git a/feature/gnoi/healthz/feature.textproto b/feature/gnoi/healthz/feature.textproto deleted file mode 100644 index b7fcef86a4e..00000000000 --- a/feature/gnoi/healthz/feature.textproto +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_healthz" - version: 1 -} - -gnoi_service { - service_name: "gnoi.healthz.Healthz" - method_name: "Get" -} -gnoi_service { - service_name: "gnoi.healthz.Healthz" - method_name: "List" -} -gnoi_service { - service_name: "gnoi.healthz.Healthz" - method_name: "Acknowledge" -} -gnoi_service { - service_name: "gnoi.healthz.Healthz" - method_name: "Artifact" -} -gnoi_service { - service_name: "gnoi.healthz.Healthz" - method_name: "Check" -} diff --git a/feature/gnoi/os/feature.textproto b/feature/gnoi/os/feature.textproto deleted file mode 100644 index 765f2044a91..00000000000 --- a/feature/gnoi/os/feature.textproto +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_os" - version: 1 -} - -gnoi_service { - service_name: "gnoi.os.OS" - method_name: "Install" -} - -gnoi_service { - service_name: "gnoi.os.OS" - method_name: "Activate" -} - -gnoi_service { - service_name: "gnoi.os.OS" - method_name: "Verify" -} diff --git a/feature/gnoi/packet_link_qualification/feature.textproto b/feature/gnoi/packet_link_qualification/feature.textproto deleted file mode 100644 index e03f8b71b8f..00000000000 --- a/feature/gnoi/packet_link_qualification/feature.textproto +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_packet_link_qualification" - version: 1 -} - -gnoi_service { - service_name: "gnoi.gnoi_packet_link_qualification.LinkQualification" - method_name: "Create" -} -gnoi_service { - service_name: "gnoi.gnoi_packet_link_qualification.LinkQualification" - method_name: "Get" -} -gnoi_service { - service_name: "gnoi.gnoi_packet_link_qualification.LinkQualification" - method_name: "Capabilities" -} -gnoi_service { - service_name: "gnoi.gnoi_packet_link_qualification.LinkQualification" - method_name: "Delete" -} -gnoi_service { - service_name: "gnoi.gnoi_packet_link_qualification.LinkQualification" - method_name: "List" -} diff --git a/feature/gnoi/bootconfig/tests/README.md b/feature/gnoi/secure_boot/tests/bootconfig/README.md similarity index 97% rename from feature/gnoi/bootconfig/tests/README.md rename to feature/gnoi/secure_boot/tests/bootconfig/README.md index 433d245dcf3..d97f3700ba2 100644 --- a/feature/gnoi/bootconfig/tests/README.md +++ b/feature/gnoi/secure_boot/tests/bootconfig/README.md @@ -103,6 +103,17 @@ certz { 9. Make call to gnsi.Credentialz.Rotate - this should fail since the vc namespace should take precedence. +## Canonical OC +```json +{ + "system": { + "config": { + "hostname": "hostname" + } + } +} +``` + ## 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. diff --git a/feature/gnoi/secure_boot/tests/bootconfig/metadata.textproto b/feature/gnoi/secure_boot/tests/bootconfig/metadata.textproto new file mode 100644 index 00000000000..71a1e495489 --- /dev/null +++ b/feature/gnoi/secure_boot/tests/bootconfig/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "0fffe17e-ff1e-403f-98f7-f66c94fb1699" +plan_id: "gNOI-7.1" +description: "BootConfig" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/gnoi/system/feature.textproto b/feature/gnoi/system/feature.textproto deleted file mode 100644 index 32f544193ad..00000000000 --- a/feature/gnoi/system/feature.textproto +++ /dev/null @@ -1,55 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gnoi_system" - version: 1 -} - -gnoi_service { - service_name: "gnoi.system.System" - method_name: "Ping" -} - -gnoi_service { - service_name: "gnoi.system.System" - method_name: "Traceroute" -} - -gnoi_service { - service_name: "gnoi.system.System" - method_name: "SwitchControlProcessor" -} - -gnoi_service { - service_name: "gnoi.system.System" - method_name: "Reboot" -} - -gnoi_service { - service_name: "gnoi.system.System" - method_name: "RebootStatus" -} - -gnoi_service { - service_name: "gnoi.system.System" - method_name: "CancelReboot" -} -gnoi_service { - service_name: "gnoi.system.System" - method_name: "KillProcess" -} 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 27bbc03efb5..202f61e79c0 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 @@ -113,7 +113,6 @@ func TestCopyingDebugFiles(t *testing.T) { req := &hpb.GetRequest{ Path: &tpb.Path{ - Origin: "openconfig", Elem: []*tpb.PathElem{ { Name: "components", @@ -125,6 +124,9 @@ func TestCopyingDebugFiles(t *testing.T) { }, }, } + if !deviations.SkipOrigin(dut) { + req.Path.Origin = "openconfig" + } validResponse, err := gnoiClient.Healthz().Get(context.Background(), req) t.Logf("Error: %v", err) switch dut.Vendor() { @@ -145,7 +147,6 @@ func TestChassisComponentArtifacts(t *testing.T) { // Execute Healthz Check RPC for the chassis component. chkReq := &hpb.CheckRequest{ Path: &tpb.Path{ - Origin: "openconfig", Elem: []*tpb.PathElem{ { Name: "components", @@ -157,6 +158,9 @@ func TestChassisComponentArtifacts(t *testing.T) { }, }, } + if !deviations.SkipOrigin(dut) { + chkReq.Path.Origin = "openconfig" + } t.Logf("Executing Healthz Check RPC for component %v", componentName["name"]) chkRes, err := gnoiClient.Healthz().Check(context.Background(), chkReq) if err != nil { diff --git a/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto b/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto index fc3f0cd55ac..8d288eec4ae 100644 --- a/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto +++ b/feature/gnoi/system/tests/copying_debug_files_test/metadata.textproto @@ -12,4 +12,12 @@ platform_exceptions: { deviations: { chassis_get_rpc_unsupported: true } -} \ No newline at end of file +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + skip_origin: true + } +} diff --git a/feature/gnoi/system/tests/per_component_reboot_test/per_component_reboot_test.go b/feature/gnoi/system/tests/per_component_reboot_test/per_component_reboot_test.go index 32d650c4543..8d92b9dcb3b 100644 --- a/feature/gnoi/system/tests/per_component_reboot_test/per_component_reboot_test.go +++ b/feature/gnoi/system/tests/per_component_reboot_test/per_component_reboot_test.go @@ -375,7 +375,7 @@ func testTrafficDrop(t *testing.T, dut *ondatra.DUTDevice, linecard string) { // fpcFromPort extracts the FPC name from a Juniper port name. func fpcFromPort(t testing.TB, portName string) (string, error) { t.Helper() - re := regexp.MustCompile(`^[a-z]+-(\d+)/\d+/\d+$`) + re := regexp.MustCompile(`^[a-z]+-(\d+)/\d+/\d+(?::\d+)?$`) match := re.FindStringSubmatch(portName) if match == nil { return "", fmt.Errorf("invalid port name format: %s", portName) diff --git a/feature/gnpsi/otg_tests/sampling_test/README.md b/feature/gnpsi/otg_tests/sampling_test/README.md index 46a38506db1..300c305bc57 100644 --- a/feature/gnpsi/otg_tests/sampling_test/README.md +++ b/feature/gnpsi/otg_tests/sampling_test/README.md @@ -16,8 +16,6 @@ The goal is to validate that packet sampling is working as expected, clients can * Varying packet sizes (64, 512, 1500) * Start OTG traffic -TODO: Add gNPSI client support to OTG. - ### gNPSI 1.1: Validate DUT configuration of gNPSI server, connect OTG client and verify samples. * Start the gRPC client and subscribe to the gNPSI service on the DUT. @@ -30,21 +28,21 @@ TODO: Add gNPSI client support to OTG. * Ingress and egress interfaces are correct * Inner packets can be parsed correctly -### gNPSI 1.2: Verify multiple clients can connect to the gNPSI Service and receive samples. +### gNPSI-1.2: Verify multiple clients can connect to the gNPSI Service and receive samples. 1. Start 2 gRPC clients and subscribe to the gNPSI service on the DUT. 2. Verify each client receives ~1 sample for every 1M packet through the DUT. -### gNPSI 1.3: Verify client reconnection to the gNPSI service. +### gNPSI-1.3: Verify client reconnection to the gNPSI service. * Start a gRPC client and subscribe to the gRPC service on the DUT, and verify the connection is healthy and samples are received. * Disconnect and reconnect the client, and verifying the reconnection is successful, and samples are received. -### gNPSI 1.4: Verify client connection after gNPSI service restart. +### gNPSI-1.4: Verify client connection after gNPSI service restart. * Start a gRPC client and subscribe to the gRPC service on the DUT, and verify the connection is healthy and samples are received. diff --git a/feature/gnpsi/otg_tests/sampling_test/metadata.textproto b/feature/gnpsi/otg_tests/sampling_test/metadata.textproto new file mode 100644 index 00000000000..e77b80c5a1b --- /dev/null +++ b/feature/gnpsi/otg_tests/sampling_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "fa50639c-09bb-4f24-ba35-a7350bb4ff34" +plan_id: "gNPSI-1" +description: "Sampling and Subscription Check" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/README.md b/feature/gnsi/acctz/README.md similarity index 88% rename from feature/security/gnsi/acctz/README.md rename to feature/gnsi/acctz/README.md index 4286a891888..37ac80ea180 100644 --- a/feature/security/gnsi/acctz/README.md +++ b/feature/gnsi/acctz/README.md @@ -38,9 +38,9 @@ Create a library of device configuration to be used for all of the gNSI.acctz.v1 - [ACCTZ-3.1 Record Subscribe Non-gRPC](tests/record_subscribe_non_grpc) - [ACCTZ-4.1 Record History Truncation](tests/record_history_truncation) - [ACCTZ-4.2 Record Payload Truncation](tests/record_payload_truncation) -- [ACCTZ-5.1 Record Subscribe Idle Timeout](RecordSubscribeIdleTimeout/) -- [ACCTZ-6.1 Record Subscribe Idle Timeout DoA](RecordSubscribeIdleTimeoutDoA/) -- [ACCTZ-7.1 Accounting Authentication Failure - Multi-transaction](AccountingAuthenFailMulti/) -- [ACCTZ-8.1 Accounting Authentication Failure - Uni-transaction](AccountingAuthenFailUni/) -- [ACCTZ-9.1 Accounting Privilege Escalation](AccountingPrivEscalation/) -- [ACCTZ-10.1 Accounting Authentication Error - Multi-transaction](AccountingAuthenErrorMulti/) +- [ACCTZ-5.1 Record Subscribe Idle Timeout](tests/RecordSubscribeIdleTimeout/) +- [ACCTZ-6.1 Record Subscribe Idle Timeout DoA](tests/RecordSubscribeIdleTimeoutDoA/) +- [ACCTZ-7.1 Accounting Authentication Failure - Multi-transaction](tests/AccountingAuthenFailMulti/) +- [ACCTZ-8.1 Accounting Authentication Failure - Uni-transaction](tests/AccountingAuthenFailUni/) +- [ACCTZ-9.1 Accounting Privilege Escalation](tests/AccountingPrivEscalation/) +- [ACCTZ-10.1 Accounting Authentication Error - Multi-transaction](tests/AccountingAuthenErrorMulti/) diff --git a/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md b/feature/gnsi/acctz/tests/AccountingAuthenErrorMulti/README.md similarity index 96% rename from feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md rename to feature/gnsi/acctz/tests/AccountingAuthenErrorMulti/README.md index bf0d49cfce4..c9a0653b973 100644 --- a/feature/security/gnsi/acctz/AccountingAuthenErrorMulti/README.md +++ b/feature/gnsi/acctz/tests/AccountingAuthenErrorMulti/README.md @@ -1,4 +1,4 @@ -# ACCTZ-10.1 - gNSI.acctz.v1 (Accounting) Test Accounting Authentication Error - Multi-transaction +# ACCTZ-10.1: gNSI.acctz.v1 (Accounting) Test Accounting Authentication Error - Multi-transaction ## Summary Test Accounting for authentication errors in the authentication service diff --git a/feature/gnsi/acctz/tests/AccountingAuthenErrorMulti/metadata.textproto b/feature/gnsi/acctz/tests/AccountingAuthenErrorMulti/metadata.textproto new file mode 100644 index 00000000000..5cbe07c03c2 --- /dev/null +++ b/feature/gnsi/acctz/tests/AccountingAuthenErrorMulti/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1b955b4a-40f0-44e1-b1d4-21f71b02a926" +plan_id: "ACCTZ-10.1" +description: "gNSI.acctz.v1 (Accounting) Test Accounting Authentication Error - Multi-transaction" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md b/feature/gnsi/acctz/tests/AccountingAuthenFailMulti/README.md similarity index 96% rename from feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md rename to feature/gnsi/acctz/tests/AccountingAuthenFailMulti/README.md index 3ca077975e6..22e05bc508e 100644 --- a/feature/security/gnsi/acctz/AccountingAuthenFailMulti/README.md +++ b/feature/gnsi/acctz/tests/AccountingAuthenFailMulti/README.md @@ -1,4 +1,4 @@ -# ACCTZ-7.1 - gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Multi-transaction +# ACCTZ-7.1: gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Multi-transaction ## Summary Test Accounting for authentication failures of multi-transaction logins diff --git a/feature/gnsi/acctz/tests/AccountingAuthenFailMulti/metadata.textproto b/feature/gnsi/acctz/tests/AccountingAuthenFailMulti/metadata.textproto new file mode 100644 index 00000000000..48d356ac4c3 --- /dev/null +++ b/feature/gnsi/acctz/tests/AccountingAuthenFailMulti/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "06b221ae-2709-4e75-88d6-5ffebb4c6bbd" +plan_id: "ACCTZ-7.1" +description: "gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Multi-transaction" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md b/feature/gnsi/acctz/tests/AccountingAuthenFailUni/README.md similarity index 96% rename from feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md rename to feature/gnsi/acctz/tests/AccountingAuthenFailUni/README.md index 490e690223f..a0530428288 100644 --- a/feature/security/gnsi/acctz/AccountingAuthenFailUni/README.md +++ b/feature/gnsi/acctz/tests/AccountingAuthenFailUni/README.md @@ -1,4 +1,4 @@ -# ACCTZ-8.1 - gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Uni-transaction +# ACCTZ-8.1: gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Uni-transaction ## Summary Test Accounting for authentication failures of uni-transaction logins diff --git a/feature/gnsi/acctz/tests/AccountingAuthenFailUni/metadata.textproto b/feature/gnsi/acctz/tests/AccountingAuthenFailUni/metadata.textproto new file mode 100644 index 00000000000..296003ea0e4 --- /dev/null +++ b/feature/gnsi/acctz/tests/AccountingAuthenFailUni/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f792f294-7bac-4278-bb0c-9e039b223d09" +plan_id: "ACCTZ-8.1" +description: "gNSI.acctz.v1 (Accounting) Test Accounting Authentication Failure - Uni-transaction" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md b/feature/gnsi/acctz/tests/AccountingPrivEscalation/README.md similarity index 96% rename from feature/security/gnsi/acctz/AccountingPrivEscalation/README.md rename to feature/gnsi/acctz/tests/AccountingPrivEscalation/README.md index 80795e029df..d40c74d2ed2 100644 --- a/feature/security/gnsi/acctz/AccountingPrivEscalation/README.md +++ b/feature/gnsi/acctz/tests/AccountingPrivEscalation/README.md @@ -1,4 +1,4 @@ -# ACCTZ-9.1 - gNSI.acctz.v1 (Accounting) Test Accounting Privilege Escalation +# ACCTZ-9.1: gNSI.acctz.v1 (Accounting) Test Accounting Privilege Escalation ## Summary Test Accounting for changing current privilege level, if supported. diff --git a/feature/gnsi/acctz/tests/AccountingPrivEscalation/metadata.textproto b/feature/gnsi/acctz/tests/AccountingPrivEscalation/metadata.textproto new file mode 100644 index 00000000000..8c959d70b9f --- /dev/null +++ b/feature/gnsi/acctz/tests/AccountingPrivEscalation/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "b14b5c1c-4b88-4c8a-9144-b83cbdb27dc5" +plan_id: "ACCTZ-9.1" +description: "gNSI.acctz.v1 (Accounting) Test Accounting Privilege Escalation" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeout/README.md similarity index 90% rename from feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md rename to feature/gnsi/acctz/tests/RecordSubscribeIdleTimeout/README.md index b7775f06814..73407a8eea7 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeout/README.md +++ b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeout/README.md @@ -1,4 +1,4 @@ -# ACCTZ-5.1 - gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - client becomes silent +# ACCTZ-5.1: gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - client becomes silent ## Summary Test RecordSubscribe connection termination after idle timeout following 1 RecordSubscribe RPC and 1 idle timeout refresh RPC diff --git a/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeout/metadata.textproto b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeout/metadata.textproto new file mode 100644 index 00000000000..05583fd1c0b --- /dev/null +++ b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeout/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5842851c-5be1-4aab-90e0-bb19d63c8488" +plan_id: "ACCTZ-5.1" +description: "gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - client becomes silent" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeoutDoA/README.md similarity index 88% rename from feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md rename to feature/gnsi/acctz/tests/RecordSubscribeIdleTimeoutDoA/README.md index df8d552e8ad..044667ff90b 100644 --- a/feature/security/gnsi/acctz/RecordSubscribeIdleTimeoutDoA/README.md +++ b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeoutDoA/README.md @@ -1,4 +1,4 @@ -# ACCTZ-6.1 - gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - DoA client +# ACCTZ-6.1: gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - DoA client ## Summary Test RecordSubscribe connection termination after idle timeout without making RecordSubscribe idle timeout refresh RPCs diff --git a/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeoutDoA/metadata.textproto b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeoutDoA/metadata.textproto new file mode 100644 index 00000000000..b0fd6144a4a --- /dev/null +++ b/feature/gnsi/acctz/tests/RecordSubscribeIdleTimeoutDoA/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "85c3cba5-3e2e-4727-9473-3293c7bab06d" +plan_id: "ACCTZ-6.1" +description: "gNSI.acctz.v1 (Accounting) Test RecordSubscribe Idle Timeout - DoA client" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/tests/record_history_truncation/README.md b/feature/gnsi/acctz/tests/record_history_truncation/README.md similarity index 100% rename from feature/security/gnsi/acctz/tests/record_history_truncation/README.md rename to feature/gnsi/acctz/tests/record_history_truncation/README.md diff --git a/feature/security/gnsi/acctz/tests/record_history_truncation/metadata.textproto b/feature/gnsi/acctz/tests/record_history_truncation/metadata.textproto similarity index 100% rename from feature/security/gnsi/acctz/tests/record_history_truncation/metadata.textproto rename to feature/gnsi/acctz/tests/record_history_truncation/metadata.textproto diff --git a/feature/security/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go b/feature/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go similarity index 84% rename from feature/security/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go rename to feature/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go index 1c14c9ef1ab..30a524ffff2 100644 --- a/feature/security/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go +++ b/feature/gnsi/acctz/tests/record_history_truncation/record_history_truncation_test.go @@ -33,25 +33,17 @@ func TestMain(m *testing.M) { func TestAccountzRecordHistoryTruncation(t *testing.T) { dut := ondatra.DUT(t, "dut") - systemState := gnmi.Get(t, dut, gnmi.OC().System().State()) - - bootTime := systemState.GetBootTime() + bootTime := gnmi.Get(t, dut, gnmi.OC().System().BootTime().State()) // Try to get records from 1 day prior to device's boot time. recordStartTime := time.Unix(0, int64(bootTime)).Add(-24 * time.Hour) - acctzClient := dut.RawAPIs().GNSI(t).Acctz() - - acctzSubClient, err := acctzClient.RecordSubscribe(context.Background()) - if err != nil { - t.Fatalf("Failed getting accountz record subscribe client, error: %s", err) - } - - err = acctzSubClient.Send(&acctzpb.RecordRequest{ + acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() + acctzSubClient, err := acctzClient.RecordSubscribe(context.Background(), &acctzpb.RecordRequest{ Timestamp: timestamppb.New(recordStartTime), }) if err != nil { - t.Fatalf("Failed sending record request, error: %s", err) + t.Fatalf("Failed getting accountz record subscribe client, error: %s", err) } record, err := acctzSubClient.Recv() diff --git a/feature/security/gnsi/acctz/tests/record_payload_truncation/README.md b/feature/gnsi/acctz/tests/record_payload_truncation/README.md similarity index 100% rename from feature/security/gnsi/acctz/tests/record_payload_truncation/README.md rename to feature/gnsi/acctz/tests/record_payload_truncation/README.md diff --git a/feature/security/gnsi/acctz/tests/record_payload_truncation/metadata.textproto b/feature/gnsi/acctz/tests/record_payload_truncation/metadata.textproto similarity index 65% rename from feature/security/gnsi/acctz/tests/record_payload_truncation/metadata.textproto rename to feature/gnsi/acctz/tests/record_payload_truncation/metadata.textproto index 714851304ee..cc0856a8c83 100644 --- a/feature/security/gnsi/acctz/tests/record_payload_truncation/metadata.textproto +++ b/feature/gnsi/acctz/tests/record_payload_truncation/metadata.textproto @@ -5,4 +5,12 @@ uuid: "ae09edfd-f64e-4095-b56d-021ce29b659b" plan_id: "ACCTZ-4.2" description: "Record Payload Truncation" testbed: TESTBED_DUT -platform_exceptions: {} + +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + static_protocol_name: "static" + } +} diff --git a/feature/security/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go b/feature/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go similarity index 75% rename from feature/security/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go rename to feature/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go index ae532753f6b..063214810f5 100644 --- a/feature/security/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go +++ b/feature/gnsi/acctz/tests/record_payload_truncation/record_payload_truncation_test.go @@ -20,7 +20,9 @@ import ( "testing" "time" + "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/helpers" acctzpb "github.com/openconfig/gnsi/acctz" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" @@ -37,6 +39,11 @@ type recordRequestResult struct { err error } +const ( + queueSize = 10 + historyMemory = 10 +) + func sendOversizedPayload(t *testing.T, dut *ondatra.DUTDevice) { // Perhaps other vendors will need a different payload/size/etc., for now we'll just send a // giant set of network instances + static routes which should hopefully work for everyone. @@ -46,55 +53,64 @@ func sendOversizedPayload(t *testing.T, dut *ondatra.DUTDevice) { ni := ocRoot.GetOrCreateNetworkInstance(fmt.Sprintf("acctz-test-ni-%d", i)) ni.SetDescription("This is a pointlessly long description in order to make the payload bigger.") ni.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF) - staticProtocol := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, "static") + staticProtocol := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + nhAddress := fmt.Sprintf("192.%d.2.1", i) for j := 0; j < 254; j++ { - staticProtocol.GetOrCreateStatic(fmt.Sprintf("10.%d.0.0/24", j)) + sr1 := staticProtocol.GetOrCreateStatic(fmt.Sprintf("10.%d.0.0/24", j)) + nh1 := sr1.GetOrCreateNextHop("0") + nh1.NextHop = oc.UnionString(nhAddress) } } - gnmi.Update(t, dut, gnmi.OC().Config(), ocRoot) } func TestAccountzRecordPayloadTruncation(t *testing.T) { dut := ondatra.DUT(t, "dut") startTime := time.Now() - sendOversizedPayload(t, dut) - acctzClient := dut.RawAPIs().GNSI(t).Acctz() - - acctzSubClient, err := acctzClient.RecordSubscribe(context.Background()) - if err != nil { - t.Fatalf("Failed getting accountz record subscribe client, error: %s", err) + t.Logf("Vendor: %s", dut.Vendor()) + switch dut.Vendor() { + case ondatra.CISCO: + communitySetCLIConfig := fmt.Sprintf("grpc \n aaa accounting queue-size %d\n aaa accounting history-memory %d \n!", queueSize, historyMemory) + helpers.GnmiCLIConfig(t, dut, communitySetCLIConfig) } + sendOversizedPayload(t, dut) + acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() - err = acctzSubClient.Send(&acctzpb.RecordRequest{ + acctzSubClient, err := acctzClient.RecordSubscribe(context.Background(), &acctzpb.RecordRequest{ Timestamp: timestamppb.New(startTime), }) if err != nil { - t.Fatalf("Failed sending record request, error: %s", err) + t.Fatalf("Failed getting accountz record subscribe client, error: %s", err) } - for { r := make(chan recordRequestResult) - go func(r chan recordRequestResult) { var response *acctzpb.RecordResponse response, err = acctzSubClient.Recv() + if err != nil { + r <- recordRequestResult{ + err: err, + } + return + } + r <- recordRequestResult{ record: response, err: err, } }(r) - var done bool var resp recordRequestResult select { case rr := <-r: resp = rr - case <-time.After(10 * time.Second): + if resp.err != nil { + t.Fatalf("Failed receiving record response, error: %s", resp.err) + } + case <-time.After(30 * time.Second): done = true } - if done { t.Fatal("Done receiving records and did not find our record...") } diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_full/README.md b/feature/gnsi/acctz/tests/record_subscribe_full/README.md similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_full/README.md rename to feature/gnsi/acctz/tests/record_subscribe_full/README.md diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_full/metadata.textproto b/feature/gnsi/acctz/tests/record_subscribe_full/metadata.textproto similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_full/metadata.textproto rename to feature/gnsi/acctz/tests/record_subscribe_full/metadata.textproto diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go b/feature/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go rename to feature/gnsi/acctz/tests/record_subscribe_full/record_subscribe_full_test.go diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/README.md b/feature/gnsi/acctz/tests/record_subscribe_non_grpc/README.md similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/README.md rename to feature/gnsi/acctz/tests/record_subscribe_non_grpc/README.md diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto b/feature/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto rename to feature/gnsi/acctz/tests/record_subscribe_non_grpc/metadata.textproto diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go b/feature/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go rename to feature/gnsi/acctz/tests/record_subscribe_non_grpc/record_subscribe_non_grpc_test.go diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_partial/README.md b/feature/gnsi/acctz/tests/record_subscribe_partial/README.md similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_partial/README.md rename to feature/gnsi/acctz/tests/record_subscribe_partial/README.md diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto b/feature/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto rename to feature/gnsi/acctz/tests/record_subscribe_partial/metadata.textproto diff --git a/feature/security/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go b/feature/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go similarity index 100% rename from feature/security/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go rename to feature/gnsi/acctz/tests/record_subscribe_partial/record_subscribe_partial_test.go diff --git a/feature/security/gnsi/authz/tests/authz/README.md b/feature/gnsi/authz/tests/authz/README.md similarity index 91% rename from feature/security/gnsi/authz/tests/authz/README.md rename to feature/gnsi/authz/tests/authz/README.md index 92cb5022af9..7dbc6100e7f 100644 --- a/feature/security/gnsi/authz/tests/authz/README.md +++ b/feature/gnsi/authz/tests/authz/README.md @@ -4,13 +4,18 @@ Test gNSI API behaviors and gRPC authorization policy behaviors. -## Baseline Setup +## Testbed type +* [`featureprofiles/topologies/dut.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed) -### Input Args +## Procedure + +### Baseline Setup + +#### Input Args * `test_infra_id`: the SPIFFE-ID that is used by test infra clients. -### DUT service setup +#### DUT service setup Configure the DUT to enable the following services (that are using gRPC) are up, and use mTLS for authentication: @@ -21,7 +26,7 @@ Configure the DUT to enable the following services (that are using gRPC) are up, NOTE: the support of SPIFFE-ID should NOT require explicitly pre-configured local users in the DUT config (for the purpose of 1:1 mapping of each SPIFFE-ID to a local user). -### Client certs +#### Client certs Prepare the following certs with the specified SPIFFE ID. Cert format details can be found in [SPIFFE PR](https://github.com/openconfig/featureprofiles/pull/1563/files) @@ -34,7 +39,7 @@ Prepare the following certs with the specified SPIFFE ID. Cert format details ca * `cert_gnsi_probe` with `spiffe://test-abc.foo.bar/xyz/gnsi-probe` * `cert_read_only` with `spiffe://test-abc.foo.bar/xyz/read-only` -### gRPC authorization policies +#### gRPC authorization policies NOTE: unless specifically mentioned, the rule `allow-test-infra` MUST be attached to all the policies, so that the test or the test infra is not blocked from the device. @@ -401,15 +406,57 @@ For each of the scenarios in this section, we need to exercise the following 3 a 3. Reconnect to the device, issue `gNSI.Get` and `gNMI.Get` and validate the value of `version`, `created_on` and gRPC policy content does not change. 4. Ensure actual corresponding clients are authorized per the the above table for policy `policy-normal-1`. -## OpenConfig Path and RPC Coverage +## Canonical OC +```json +{ + "system": { + "aaa": { + "authentication": { + "users": { + "user": [ + { + "config": { + "password": "xxxxxxx", + "ssh-key": "yyyyyyy", + "username": "testuser" + }, + "username": "testuser" + } + ] + } + } + } + } +} +``` -The below yaml defines the OC paths intended to be covered by this test. OC paths used for test setup are not listed here. + +## OpenConfig Path and RPC Coverage TODO(OCRPC): Record is not complete +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### +/system/aaa/authentication/users/user/config: +/system/aaa/authentication/users/user/config/username: +/system/aaa/authentication/users/user/config/password: + +###State paths### +/system/aaa/authentication/users/user/state: +/system/aaa/authentication/users/user/state/username: +/system/aaa/authentication/users/user/state/password: +/system/aaa/authentication/users/user/state/authorized-principals-list-version: +/system/aaa/authentication/users/user/state/authorized-principals-list-created-on: + rpcs: gnsi: - authz.v1.Authz.Get: + credentialz.v1.Credentialz.RotateAccountCredentials: + ``` +## Minimum DUT platform requirement +* KNE + diff --git a/feature/security/gnsi/authz/tests/authz/authz1_4_test.go b/feature/gnsi/authz/tests/authz/authz1_4_test.go similarity index 99% rename from feature/security/gnsi/authz/tests/authz/authz1_4_test.go rename to feature/gnsi/authz/tests/authz/authz1_4_test.go index ec6f2008aa6..e38d266e8c8 100644 --- a/feature/security/gnsi/authz/tests/authz/authz1_4_test.go +++ b/feature/gnsi/authz/tests/authz/authz1_4_test.go @@ -87,7 +87,7 @@ type access struct { type authorizationTable map[string]access var authTable = authorizationTable{ - //table: map[string]access{ + // table: map[string]access{ "cert_user_admin": struct { allowed []*gnxi.RPC denied []*gnxi.RPC @@ -213,7 +213,8 @@ func TestAuthz1(t *testing.T) { if !ok { t.Fatal("Policy policy-everyone-can-gnmi-not-gribi is not loaded from policy json file") } - newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.AllRPC}) + + newpolicy.AddAllowRules("base", []string{*testInfraID}, []*gnxi.RPC{gnxi.RPCs.GnsiAuthzAllRPC}) // Rotate the policy. newpolicy.Rotate(t, dut, uint64(100), "policy-everyone-can-gnmi-not-gribi_v1", false) @@ -684,6 +685,7 @@ func TestAuthz4(t *testing.T) { t.Logf("Wait for DUT to boot up by polling the telemetry output.") for { t.Logf("Time elapsed %.2f seconds since reboot started.", time.Since(startReboot).Seconds()) + time.Sleep(time.Minute) if errMsg := testt.CaptureFatal(t, func(t testing.TB) { currentTime = gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State()) }); errMsg != nil { diff --git a/feature/security/gnsi/authz/tests/authz/metadata.textproto b/feature/gnsi/authz/tests/authz/metadata.textproto similarity index 86% rename from feature/security/gnsi/authz/tests/authz/metadata.textproto rename to feature/gnsi/authz/tests/authz/metadata.textproto index 8e841aa56e7..757ee4864e8 100644 --- a/feature/security/gnsi/authz/tests/authz/metadata.textproto +++ b/feature/gnsi/authz/tests/authz/metadata.textproto @@ -4,4 +4,4 @@ uuid: "a5c2d46c-976b-41bd-9fae-3341ce5c1b29" plan_id: "Authz" description: "General Authz (1-4) tests" -testbed: TESTBED_DUT_ATE_2LINKS +testbed: TESTBED_DUT diff --git a/feature/security/gnsi/authz/tests/authz/testdata/ca.cert.pem b/feature/gnsi/authz/tests/authz/testdata/ca.cert.pem similarity index 100% rename from feature/security/gnsi/authz/tests/authz/testdata/ca.cert.pem rename to feature/gnsi/authz/tests/authz/testdata/ca.cert.pem diff --git a/feature/security/gnsi/authz/tests/authz/testdata/ca.key.pem b/feature/gnsi/authz/tests/authz/testdata/ca.key.pem similarity index 100% rename from feature/security/gnsi/authz/tests/authz/testdata/ca.key.pem rename to feature/gnsi/authz/tests/authz/testdata/ca.key.pem diff --git a/feature/security/gnsi/authz/tests/authz/testdata/policy.json b/feature/gnsi/authz/tests/authz/testdata/policy.json similarity index 100% rename from feature/security/gnsi/authz/tests/authz/testdata/policy.json rename to feature/gnsi/authz/tests/authz/testdata/policy.json diff --git a/feature/security/gnsi/certz/test_data/README.md b/feature/gnsi/certz/test_data/README.md similarity index 100% rename from feature/security/gnsi/certz/test_data/README.md rename to feature/gnsi/certz/test_data/README.md diff --git a/feature/security/gnsi/certz/test_data/cleanup.sh b/feature/gnsi/certz/test_data/cleanup.sh similarity index 100% rename from feature/security/gnsi/certz/test_data/cleanup.sh rename to feature/gnsi/certz/test_data/cleanup.sh diff --git a/feature/security/gnsi/certz/test_data/client_cert.cnf b/feature/gnsi/certz/test_data/client_cert.cnf similarity index 100% rename from feature/security/gnsi/certz/test_data/client_cert.cnf rename to feature/gnsi/certz/test_data/client_cert.cnf diff --git a/feature/security/gnsi/certz/test_data/client_cert_ext.cnf b/feature/gnsi/certz/test_data/client_cert_ext.cnf similarity index 100% rename from feature/security/gnsi/certz/test_data/client_cert_ext.cnf rename to feature/gnsi/certz/test_data/client_cert_ext.cnf diff --git a/feature/security/gnsi/certz/test_data/mk_cas.sh b/feature/gnsi/certz/test_data/mk_cas.sh similarity index 100% rename from feature/security/gnsi/certz/test_data/mk_cas.sh rename to feature/gnsi/certz/test_data/mk_cas.sh diff --git a/feature/security/gnsi/certz/test_data/server_cert.cnf b/feature/gnsi/certz/test_data/server_cert.cnf similarity index 100% rename from feature/security/gnsi/certz/test_data/server_cert.cnf rename to feature/gnsi/certz/test_data/server_cert.cnf diff --git a/feature/security/gnsi/certz/test_data/server_cert_ext.cnf b/feature/gnsi/certz/test_data/server_cert_ext.cnf similarity index 100% rename from feature/security/gnsi/certz/test_data/server_cert_ext.cnf rename to feature/gnsi/certz/test_data/server_cert_ext.cnf diff --git a/feature/security/gnsi/certz/client_certificates/README.md b/feature/gnsi/certz/tests/client_certificates/README.md similarity index 98% rename from feature/security/gnsi/certz/client_certificates/README.md rename to feature/gnsi/certz/tests/client_certificates/README.md index 0efdc4f8f6c..7745f640f79 100644 --- a/feature/security/gnsi/certz/client_certificates/README.md +++ b/feature/gnsi/certz/tests/client_certificates/README.md @@ -1,4 +1,4 @@ -# gNSI Client Certificate Tests +# Certz-1: gNSI Client Certificate Tests ## Summary diff --git a/feature/gnsi/certz/tests/client_certificates/metadata.textproto b/feature/gnsi/certz/tests/client_certificates/metadata.textproto new file mode 100644 index 00000000000..b6743a0d191 --- /dev/null +++ b/feature/gnsi/certz/tests/client_certificates/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "b98eb655-8b22-49d3-b0f4-4df12292685a" +plan_id: "Certz-1" +description: "gNSI Client Certificate Tests" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/certz/server_certificate_rotation/README.md b/feature/gnsi/certz/tests/server_certificate_rotation/README.md similarity index 98% rename from feature/security/gnsi/certz/server_certificate_rotation/README.md rename to feature/gnsi/certz/tests/server_certificate_rotation/README.md index 8a2afe1c30b..6e13f3878d8 100644 --- a/feature/security/gnsi/certz/server_certificate_rotation/README.md +++ b/feature/gnsi/certz/tests/server_certificate_rotation/README.md @@ -1,4 +1,4 @@ -# Server Certificate Rotation +# Certz-3: Server Certificate Rotation ## Summary @@ -94,8 +94,6 @@ Perform this test with both the RSA and ECDSA types. 5) Verify that the server is now serving the previous certifcate properly. - - ## 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. diff --git a/feature/gnsi/certz/tests/server_certificate_rotation/metadata.textproto b/feature/gnsi/certz/tests/server_certificate_rotation/metadata.textproto new file mode 100644 index 00000000000..6f938715ec9 --- /dev/null +++ b/feature/gnsi/certz/tests/server_certificate_rotation/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1bf7746a-e4e3-4c46-ae5a-e24c4cb63395" +plan_id: "Certz-3" +description: "Server Certificate Rotation" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/certz/server_certificates/README.md b/feature/gnsi/certz/tests/server_certificates/README.md similarity index 99% rename from feature/security/gnsi/certz/server_certificates/README.md rename to feature/gnsi/certz/tests/server_certificates/README.md index eed72366c9a..5d334a70085 100644 --- a/feature/security/gnsi/certz/server_certificates/README.md +++ b/feature/gnsi/certz/tests/server_certificates/README.md @@ -1,4 +1,4 @@ -# Server Certificate +# Certz-2: Server Certificate ## Summary diff --git a/feature/gnsi/certz/tests/server_certificates/metadata.textproto b/feature/gnsi/certz/tests/server_certificates/metadata.textproto new file mode 100644 index 00000000000..c9565549a59 --- /dev/null +++ b/feature/gnsi/certz/tests/server_certificates/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "0ced37e8-9765-4ed1-8a55-06819d6ee95c" +plan_id: "Certz-2" +description: "Server Certificate" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/gnsi/certz/tests/trust_bundle/README.md b/feature/gnsi/certz/tests/trust_bundle/README.md new file mode 100644 index 00000000000..8c32d74ee27 --- /dev/null +++ b/feature/gnsi/certz/tests/trust_bundle/README.md @@ -0,0 +1,180 @@ +# Certz-4: Trust Bundle + +## Summary + +Server and client TLS endpoints use x.509 certificates for +identification of the calling or called endpoint. Systems +could use self-signed certificates and not validate, but +this is an insecure practice. + +Servers and clients should require that the certificates +used are validated and are signed by a known Certificate +Authority(CA). + +The known CAs which can be used are contained in a +'trust bundle', which is a list of public keys of the CAs. +The list of CA public keys must be kept up to date, as +CAs will rotate their key material on a regular cadence. + +CA keys may be one of two valid (at this time) key algorithms: + + * RSA + * ECDSA + +(Note: Security of key algorithms is subject to change, the +system must be able to support more than one key type at any +point in time in order to support key algorithm change events.) + +A trust bundle may have one or more certificates contained in +it, systems should be able to support at least twenty thousand +CA keys in such a bundle. + + +## Baseline Setup + +### Input Args + + * the set of certificate testdata generated with the mk_cas.sh + script in the featureprofiles/feature/security/gnsi/certz/test_data + directory. + +### DUT service setup + +Configure the DUT to enable the following sevices (that are using gRPC) are +up and require using mTLS for authentication: + + * gNMI + * gNOI + * gNSI + * gRIBI + * P4RT + +For each trust_bundle created by mk_cas.sh, configure the +services to load the correct key-type certificate, key and +trust_bundle. + +## Tests + +### Test Certz-4.1 + +Load the server certificate and key from each of the following CA sets: + * ca-01 + * ca-02 + * ca-10 + * ca-1000 + * ca-20000 + +Each service must be configured to use the appropriate certificate and validate +that certificate using the included trust_bundle. Loading a new trust_bundle +should not take longer than 120 seconds. + +Perform this test with both RSA dn ECDSA key-types. + +## Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "test interface", + "name": "port1" + }, + "name": "port1", + "subinterfaces": { + "subinterface": [ + { + "config": { + "index": 0 + }, + "index": 0, + "ipv4": { + "addresses": { + "address": [ + { + "config": { + "ip": "192.20.0.1", + "prefix-length": 32 + }, + "ip": "192.20.0.1" + } + ] + } + } + } + ] + } + } + ] + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "GRPC_TEST" + }, + "interfaces": { + "interface": [ + { + "config": { + "id": "port1", + "interface": "port1" + }, + "id": "port1" + } + ] + }, + "name": "GRPC_TEST" + } + ] + }, + "system": { + "grpc-servers": { + "grpc-server": [ + { + "config": { + "enable": true, + "name": "gmmi-test", + "network-instance": "GRPC_TEST", + "port": 9339, + "services": [ + "GNMI", + "GNSI", + "GRIBI" + ] + }, + "name": "gmmi-test" + }, + { + "config": { + "enable": true, + "name": "p4rt-test", + "network-instance": "GRPC_TEST", + "port": 9559, + "services": [ + "P4RT" + ] + }, + "name": "p4rt-test" + } + ] + } + } +} +``` +## 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. + +TODO(OCRPC): Record may not be correct or complete + +```yaml +rpcs: + gnsi: + certz.v1.Certz.GetProfileList: +``` + + +## Minimum DUT Platform Requirement + +vRX diff --git a/feature/gnsi/certz/tests/trust_bundle/metadata.textproto b/feature/gnsi/certz/tests/trust_bundle/metadata.textproto new file mode 100644 index 00000000000..4a4af57fd82 --- /dev/null +++ b/feature/gnsi/certz/tests/trust_bundle/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c755648a-f797-4c3c-985f-09c5f04238d4" +plan_id: "Certz-4" +description: "Trust Bundle" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/certz/trust_bundle_rotation/README.md b/feature/gnsi/certz/tests/trust_bundle_rotation/README.md similarity index 98% rename from feature/security/gnsi/certz/trust_bundle_rotation/README.md rename to feature/gnsi/certz/tests/trust_bundle_rotation/README.md index db80145a9a1..5ce3cdd2f02 100644 --- a/feature/security/gnsi/certz/trust_bundle_rotation/README.md +++ b/feature/gnsi/certz/tests/trust_bundle_rotation/README.md @@ -1,4 +1,4 @@ -# Trust Bundle Rotation +# Certz-5: Trust Bundle Rotation ## Summary @@ -81,7 +81,6 @@ Perform this test with both the RSA and ECDSA types. 5) Verify that the server is still serving the certifcate properly. - ## 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. diff --git a/feature/gnsi/certz/tests/trust_bundle_rotation/metadata.textproto b/feature/gnsi/certz/tests/trust_bundle_rotation/metadata.textproto new file mode 100644 index 00000000000..de6ec39c5de --- /dev/null +++ b/feature/gnsi/certz/tests/trust_bundle_rotation/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "5f2b3291-9cf4-4702-b7cb-0ebb2c48e900" +plan_id: "Certz-5" +description: "Trust Bundle Rotation" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/credentialz/tests/hiba_authentication/README.md b/feature/gnsi/credentialz/tests/hiba_authentication/README.md similarity index 100% rename from feature/security/gnsi/credentialz/tests/hiba_authentication/README.md rename to feature/gnsi/credentialz/tests/hiba_authentication/README.md diff --git a/feature/security/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go b/feature/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go similarity index 91% rename from feature/security/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go rename to feature/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go index a9218565a89..a1373ab4a4c 100644 --- a/feature/security/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go +++ b/feature/gnsi/credentialz/tests/hiba_authentication/hiba_authentication_test.go @@ -15,8 +15,8 @@ package hibaauthentication_test import ( + "context" "fmt" - "os" "testing" "time" @@ -46,21 +46,8 @@ func TestMain(m *testing.M) { func TestCredentialz(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := credz.GetDutTarget(t, dut) - - // Create temporary directory for storing ssh keys/certificates. - dir, err := os.MkdirTemp("", "") - if err != nil { - t.Fatalf("creating temp dir, err: %s", err) - } - defer func(dir string) { - err = os.RemoveAll(dir) - if err != nil { - t.Logf("error removing temp directory, error: %s", err) - } - }(dir) - - credz.CreateHibaKeys(t, dir) + dir := t.TempDir() + credz.CreateHibaKeys(t, dut, dir) credz.SetupUser(t, dut, username) // Set only public key authentication for our test. @@ -78,9 +65,11 @@ func TestCredentialz(t *testing.T) { } // Verify ssh with hiba fails as expected. + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() startTime := time.Now() for { - _, err := credz.SSHWithCertificate(t, target, username, fmt.Sprintf("%s/users", dir)) + _, err := credz.SSHWithCertificate(ctx, t, dut, username, fmt.Sprintf("%s/users", dir)) if err != nil { t.Logf("Dialing ssh failed as expected.") break @@ -117,10 +106,11 @@ func TestCredentialz(t *testing.T) { if !deviations.SSHServerCountersUnsupported(dut) { startingAcceptCounter, startingLastAcceptTime = credz.GetAcceptTelemetry(t, dut) } - + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() startTime := time.Now() for { - _, err := credz.SSHWithCertificate(t, target, username, fmt.Sprintf("%s/users", dir)) + _, err := credz.SSHWithCertificate(ctx, t, dut, username, fmt.Sprintf("%s/users", dir)) if err == nil { t.Logf("Dialing ssh succeeded as expected.") break diff --git a/feature/security/gnsi/credentialz/tests/hiba_authentication/metadata.textproto b/feature/gnsi/credentialz/tests/hiba_authentication/metadata.textproto similarity index 100% rename from feature/security/gnsi/credentialz/tests/hiba_authentication/metadata.textproto rename to feature/gnsi/credentialz/tests/hiba_authentication/metadata.textproto diff --git a/feature/security/gnsi/credentialz/tests/host_certificates/README.md b/feature/gnsi/credentialz/tests/host_certificates/README.md similarity index 100% rename from feature/security/gnsi/credentialz/tests/host_certificates/README.md rename to feature/gnsi/credentialz/tests/host_certificates/README.md diff --git a/feature/security/gnsi/credentialz/tests/host_certificates/host_certificates_test.go b/feature/gnsi/credentialz/tests/host_certificates/host_certificates_test.go similarity index 98% rename from feature/security/gnsi/credentialz/tests/host_certificates/host_certificates_test.go rename to feature/gnsi/credentialz/tests/host_certificates/host_certificates_test.go index f728ae0f531..c43921a65e9 100644 --- a/feature/security/gnsi/credentialz/tests/host_certificates/host_certificates_test.go +++ b/feature/gnsi/credentialz/tests/host_certificates/host_certificates_test.go @@ -64,7 +64,7 @@ func TestCredentialz(t *testing.T) { credz.RotateAuthenticationArtifacts(t, dut, dir, "", hostCertificateVersion, uint64(hostCertificateCreatedOn)) dutKey := credz.GetDutPublicKey(t, dut) - credz.CreateHostCertificate(t, dir, dutKey) + credz.CreateHostCertificate(t, dut, dir, dutKey) credz.RotateAuthenticationArtifacts(t, dut, "", dir, hostCertificateVersion, uint64(hostCertificateCreatedOn)) t.Run("dut should return signed host certificate", func(t *testing.T) { diff --git a/feature/security/gnsi/credentialz/tests/host_certificates/metadata.textproto b/feature/gnsi/credentialz/tests/host_certificates/metadata.textproto similarity index 100% rename from feature/security/gnsi/credentialz/tests/host_certificates/metadata.textproto rename to feature/gnsi/credentialz/tests/host_certificates/metadata.textproto diff --git a/feature/security/gnsi/credentialz/tests/password_console_login/README.md b/feature/gnsi/credentialz/tests/password_console_login/README.md similarity index 61% rename from feature/security/gnsi/credentialz/tests/password_console_login/README.md rename to feature/gnsi/credentialz/tests/password_console_login/README.md index 312968bb057..4010ec84eb7 100644 --- a/feature/security/gnsi/credentialz/tests/password_console_login/README.md +++ b/feature/gnsi/credentialz/tests/password_console_login/README.md @@ -5,6 +5,32 @@ Test that Credentialz properly creates users and the associated password and that the DUT handles authentication of those users properly. +## Testbed type +* [`featureprofiles/topologies/dut.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed) + +## Canonical OC +```json +{ + "system": { + "aaa": { + "authentication": { + "users": { + "user": [ + { + "config": { + "password": "xxxxxxx", + "ssh-key": "yyyyyyy", + "username": "testuser" + }, + "username": "testuser" + } + ] + } + } + } + } +} +``` ## Procedure @@ -29,12 +55,17 @@ authentication of those users properly. 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: + ## State Paths ## + /system/aaa/authentication/users/user/state/authorized-keys-list-version: + /system/aaa/authentication/users/user/state/authorized-keys-list-created-on: + /system/ssh-server/state/counters/access-accepts: + /system/ssh-server/state/counters/last-access-accept: + rpcs: gnsi: credentialz.v1.Credentialz.RotateAccountCredentials: ``` - ## Minimum DUT platform requirement - -N/A +* KNE diff --git a/feature/security/gnsi/credentialz/tests/password_console_login/metadata.textproto b/feature/gnsi/credentialz/tests/password_console_login/metadata.textproto similarity index 100% rename from feature/security/gnsi/credentialz/tests/password_console_login/metadata.textproto rename to feature/gnsi/credentialz/tests/password_console_login/metadata.textproto diff --git a/feature/security/gnsi/credentialz/tests/password_console_login/password_console_login_test.go b/feature/gnsi/credentialz/tests/password_console_login/password_console_login_test.go similarity index 80% rename from feature/security/gnsi/credentialz/tests/password_console_login/password_console_login_test.go rename to feature/gnsi/credentialz/tests/password_console_login/password_console_login_test.go index 97ac39b3c94..5a07fcb820a 100644 --- a/feature/security/gnsi/credentialz/tests/password_console_login/password_console_login_test.go +++ b/feature/gnsi/credentialz/tests/password_console_login/password_console_login_test.go @@ -15,10 +15,10 @@ package passwordconsolelogin_test import ( + "context" "testing" "time" - "github.com/google/go-cmp/cmp" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/featureprofiles/internal/fptest" @@ -41,11 +41,13 @@ func TestMain(m *testing.M) { func TestCredentialz(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := credz.GetDutTarget(t, dut) + // target := credz.GetDutTarget(t, dut) // Setup test user and password. credz.SetupUser(t, dut, username) password := credz.GeneratePassword() + + t.Logf("Rotating user password on DUT") credz.RotateUserPassword(t, dut, username, password, passwordVersion, uint64(passwordCreatedOn)) testCases := []struct { @@ -73,11 +75,12 @@ func TestCredentialz(t *testing.T) { expectFail: true, }, } - for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { // Verify ssh succeeds/fails based on expected result. - client, err := credz.SSHWithPassword(target, tc.loginUser, tc.loginPassword) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + client, err := credz.SSHWithPassword(ctx, dut, tc.loginUser, tc.loginPassword) if tc.expectFail { if err == nil { t.Fatalf("Dialing ssh succeeded, but we expected to fail.") @@ -92,18 +95,12 @@ func TestCredentialz(t *testing.T) { // Verify password telemetry. userState := gnmi.Get(t, dut, gnmi.OC().System().Aaa().Authentication().User(username).State()) gotPasswordVersion := userState.GetPasswordVersion() - if !cmp.Equal(gotPasswordVersion, passwordVersion) { - t.Fatalf( - "Telemetry reports password version is not correctn\tgot: %s\n\twant: %s", - gotPasswordVersion, passwordVersion, - ) + if got, want := gotPasswordVersion, passwordVersion; got != want { + t.Fatalf("Telemetry reports password version is not correct\n\tgot: %s\n\twant: %s", got, want) } gotPasswordCreatedOn := userState.GetPasswordCreatedOn() - if !cmp.Equal(time.Unix(0, int64(gotPasswordCreatedOn)), time.Unix(passwordCreatedOn, 0)) { - t.Fatalf( - "Telemetry reports password created on is not correct\n\tgot: %d\n\twant: %d", - gotPasswordCreatedOn, passwordCreatedOn, - ) + if got, want := gotPasswordCreatedOn, uint64(passwordCreatedOn); got != want { + t.Fatalf("Telemetry reports password created on is not correct\n\tgot: %d\n\twant: %d", got, want) } }) } diff --git a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md b/feature/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md similarity index 100% rename from feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md rename to feature/gnsi/credentialz/tests/ssh_password_login_disallowed/README.md diff --git a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto b/feature/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto similarity index 100% rename from feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto rename to feature/gnsi/credentialz/tests/ssh_password_login_disallowed/metadata.textproto diff --git a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go b/feature/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go similarity index 91% rename from feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go rename to feature/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go index 28a18ec7eee..fe17b9c54a7 100644 --- a/feature/security/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go +++ b/feature/gnsi/credentialz/tests/ssh_password_login_disallowed/ssh_password_login_disallowed_test.go @@ -18,8 +18,6 @@ import ( "context" "os" - "golang.org/x/crypto/ssh" - "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -34,6 +32,7 @@ import ( acctzpb "github.com/openconfig/gnsi/acctz" cpb "github.com/openconfig/gnsi/credentialz" "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/binding" ) const ( @@ -49,7 +48,7 @@ func TestMain(m *testing.M) { func TestCredentialz(t *testing.T) { dut := ondatra.DUT(t, "dut") - target := credz.GetDutTarget(t, dut) + // target := credz.GetDutTarget(t, dut) recordStartTime := timestamppb.New(time.Now()) // Create temporary directory for storing ssh keys/certificates. @@ -87,9 +86,11 @@ func TestCredentialz(t *testing.T) { } // Verify ssh with password fails as expected. + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() startTime := time.Now() for { - _, err := credz.SSHWithPassword(target, username, password) + _, err := credz.SSHWithPassword(ctx, dut, username, password) if err != nil { t.Logf("Dialing ssh failed as expected.") break @@ -120,10 +121,13 @@ func TestCredentialz(t *testing.T) { } // Verify ssh with certificate succeeds. + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() startTime := time.Now() - var conn *ssh.Client + // var conn *ssh.Client + var conn binding.SSHClient for { - conn, err = credz.SSHWithCertificate(t, target, username, dir) + conn, err = credz.SSHWithCertificate(ctx, t, dut, username, dir) if err == nil { t.Logf("Dialing ssh succeeded as expected.") defer conn.Close() @@ -137,12 +141,12 @@ func TestCredentialz(t *testing.T) { } // Send command for accounting. - sess, err := conn.NewSession() + sess, err := conn.RunCommand(ctx, "show version") if err != nil { t.Fatalf("Failed creating ssh session, error: %s", err) } - defer sess.Close() - sess.Run(command) + defer sess.Output() + sess.Output() // Verify ssh counters. if !deviations.SSHServerCountersUnsupported(dut) { @@ -157,7 +161,7 @@ func TestCredentialz(t *testing.T) { // Verify accounting record. acctzClient := dut.RawAPIs().GNSI(t).AcctzStream() - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second) defer cancel() acctzSubClient, err := acctzClient.RecordSubscribe(ctx, &acctzpb.RecordRequest{Timestamp: recordStartTime}) if err != nil { diff --git a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/README.md b/feature/gnsi/credentialz/tests/ssh_public_key_authentication/README.md similarity index 79% rename from feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/README.md rename to feature/gnsi/credentialz/tests/ssh_public_key_authentication/README.md index fe13bbdb82f..594eb1a3980 100644 --- a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/README.md +++ b/feature/gnsi/credentialz/tests/ssh_public_key_authentication/README.md @@ -5,6 +5,8 @@ Test that Credentialz properly configures authorized SSH public keys for a given user, and that the DUT properly allows or disallows authentication based on the configured settings. +## Testbed type +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/dut.testbed) ## Procedure @@ -28,6 +30,29 @@ the DUT properly allows or disallows authentication based on the configured sett `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` +## Canonical OC +```json +{ + "system": { + "aaa": { + "authentication": { + "users": { + "user": [ + { + "config": { + "password": "xxxxxxx", + "ssh-key": "yyyyyyy", + "username": "testuser" + }, + "username": "testuser" + } + ] + } + } + } + } +} +``` ## OpenConfig Path and RPC Coverage @@ -46,7 +71,6 @@ rpcs: credentialz.v1.Credentialz.RotateAccountCredentials: ``` - ## Minimum DUT platform requirement +* KNE -N/A \ No newline at end of file diff --git a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto b/feature/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto similarity index 100% rename from feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto rename to feature/gnsi/credentialz/tests/ssh_public_key_authentication/metadata.textproto diff --git a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go b/feature/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go similarity index 69% rename from feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go rename to feature/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go index de5530d7b20..864fe4cd529 100644 --- a/feature/security/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go +++ b/feature/gnsi/credentialz/tests/ssh_public_key_authentication/ssh_public_key_authentication_test.go @@ -15,11 +15,11 @@ package sshpublickeyauthentication_test import ( + "context" "os" "testing" "time" - "github.com/google/go-cmp/cmp" "github.com/openconfig/featureprofiles/internal/security/credz" "github.com/openconfig/featureprofiles/internal/deviations" @@ -33,9 +33,7 @@ const ( authorizedKeysListVersion = "v1.0" ) -var ( - authorizedKeysListCreatedOn = time.Now().Unix() -) +var authorizedKeysListCreatedOn int64 func TestMain(m *testing.M) { fptest.RunTests(m) @@ -44,6 +42,7 @@ func TestMain(m *testing.M) { func TestCredentialz(t *testing.T) { dut := ondatra.DUT(t, "dut") target := credz.GetDutTarget(t, dut) + authorizedKeysListCreatedOn = time.Now().Unix() // Create temporary directory for storing ssh keys/certificates. dir, err := os.MkdirTemp("", "") @@ -61,7 +60,9 @@ func TestCredentialz(t *testing.T) { credz.SetupUser(t, dut, username) t.Run("auth should fail ssh public key not authorized for user", func(t *testing.T) { - _, err := credz.SSHWithKey(t, target, username, dir) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + _, err = credz.SSHWithKey(ctx, t, dut, target, username, dir) if err == nil { t.Fatalf("Dialing ssh succeeded, but we expected to fail.") } @@ -82,42 +83,50 @@ func TestCredentialz(t *testing.T) { } // Verify ssh with key succeeds. - _, err := credz.SSHWithKey(t, target, username, dir) + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + sshClient, err := credz.SSHWithKey(ctx, t, dut, target, username, dir) if err != nil { t.Fatalf("Dialing ssh failed, but we expected to succeed. error: %v", err) } + defer sshClient.Close() + t.Logf("SSH session established") + + res, err := sshClient.RunCommand(ctx, "show version") + if err != nil { + t.Fatalf("CombinedOutput failed, err: %v", err) + } + t.Logf("SSH session output: %s", res) + sshClient.Close() + time.Sleep(2 * time.Second) // Verify ssh counters. if !deviations.SSHServerCountersUnsupported(dut) { endingAcceptCounter, endingLastAcceptTime := credz.GetAcceptTelemetry(t, dut) if endingAcceptCounter <= startingAcceptCounter { - t.Fatalf("SSH server accept counter did not increment after successful login. startCounter: %v, endCounter: %v", startingAcceptCounter, endingAcceptCounter) + t.Errorf("SSH server accept counter did not increment after successful login. startCounter: %v, endCounter: %v", startingAcceptCounter, endingAcceptCounter) } if startingLastAcceptTime == endingLastAcceptTime { - t.Fatalf("SSH server accept last timestamp did not update after successful login. Timestamp: %v", endingLastAcceptTime) + t.Errorf("SSH server accept last timestamp did not update after successful login. Timestamp: %v", endingLastAcceptTime) } } // Verify authorized keys telemetry. userState := gnmi.Get(t, dut, gnmi.OC().System().Aaa().Authentication().User(username).State()) gotAuthorizedKeysListVersion := userState.GetAuthorizedKeysListVersion() - if !cmp.Equal(gotAuthorizedKeysListVersion, authorizedKeysListVersion) { - t.Fatalf( - "Telemetry reports authorized keys list version is not correct\n\tgot: %s\n\twant: %s", - gotAuthorizedKeysListVersion, authorizedKeysListVersion, - ) + if got, want := gotAuthorizedKeysListVersion, authorizedKeysListVersion; got != want { + t.Errorf("Telemetry reports authorized keys list version is not correct, got: %s, want: %s", got, want) } - gotAuthorizedKeysListCreatedOn := userState.GetAuthorizedKeysListCreatedOn() - if !cmp.Equal(time.Unix(0, int64(gotAuthorizedKeysListCreatedOn)), time.Unix(authorizedKeysListCreatedOn, 0)) { - t.Fatalf( - "Telemetry reports authorized keys list version on is not correct\n\tgot: %d\n\twant: %d", - gotAuthorizedKeysListCreatedOn, authorizedKeysListCreatedOn, - ) + + gotAuthorizedKeysListCreatedOn := int64(userState.GetAuthorizedKeysListCreatedOn()) + if got, want := gotAuthorizedKeysListCreatedOn, authorizedKeysListCreatedOn; got != want { + t.Errorf("Telemetry reports authorized keys list created on is not correct, got: %d, want: %d", got, want) } + }) t.Cleanup(func() { // Cleanup user authorized key after test. - credz.RotateAuthorizedKey(t, dut, "", username, "", 0) + credz.RotateAuthorizedKey(t, dut, "", username, "", uint64(authorizedKeysListCreatedOn)) }) } diff --git a/feature/gribi/feature.textproto b/feature/gribi/feature.textproto deleted file mode 100644 index 6245f883738..00000000000 --- a/feature/gribi/feature.textproto +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "gribi" - version: 1 -} - -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix" -} -telemetry_path { - path: "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/programmed-id" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/backup-next-hop-group" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/programmed-index" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/ip-address" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/mac-address" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/encapsulate-header" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/decapsulate-header" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/state/network-instance" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/ip-in-ip/state/src-ip" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/ip-in-ip/state/dst-ip" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/interface" -} -telemetry_path { - path: "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/subinterface" -} diff --git a/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/README.md b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/README.md new file mode 100644 index 00000000000..35f8a74f0a7 --- /dev/null +++ b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/README.md @@ -0,0 +1,189 @@ +# TE-18.1: gRIBI MPLS-in-UDP Encapsulation + +## Summary + +Test MPLS-in-UDP encapsulation using gRIBI to create AFT entries that +match IPv6 traffic and encapsulate it with MPLS labels inside UDP +packets with IPv6 outer headers. The test validates that the DUT +correctly implements RFC 7510 MPLS-in-UDP encapsulation with +configurable UDP destination ports. + +## Topology + +ATE port-1 \<——\> port-1 DUT + +DUT port-2 \<——\> port-2 ATE + +- [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Test Configuration + +The test uses the following key parameters: + + # MPLS-in-UDP configuration + mpls_label = 100 + outer_ipv6_src = "2001:db8::1" + outer_ipv6_dst = "2001:db8::100" + outer_dst_udp_port = 6635 # RFC 7510 standard MPLS-in-UDP port + outer_ip_ttl = 64 + outer_dscp = 10 + inner_ipv6_prefix = "2001:db8:1::/64" + + # Static ARP configuration for gRIBI next hop resolution + magic_ip = "192.168.1.1" + magic_mac = "02:00:00:00:00:01" + +## Baseline Setup + +- Apply VRF selection policy to DUT port-1 to route traffic to the + DEFAULT network instance +- Set up basic static routing infrastructure using gRIBI to forward + encapsulated packets to port-2. This uses static IPv6 routes (not BGP + or ISIS) configured via gRIBI AFT entries to establish the forwarding + path for MPLS-in-UDP encapsulated packets. + +Basic routing infrastructure AFT entries: + + # Static IPv6 route for encapsulated packet forwarding + IPv6Entry {2001:db8::100/128 (DEFAULT VRF)} -> NHG#400 (DEFAULT VRF) -> { + {NH#300, DEFAULT VRF, interface: port-2, mac: magic_mac} + } + +## Procedure + +### TE-18.1.1 - Using gRIBI, install AFT entries + +Install the following MPLS-in-UDP encapsulation AFT entries: + + # MPLS-in-UDP encapsulation entries + IPv6Entry {2001:db8:1::/64 (DEFAULT VRF)} -> NHG#2001 (DEFAULT VRF) -> { + {NH#1001, DEFAULT VRF} + } + + NH#1001 -> { + encap_headers { + encap_header { + index: 1 + mpls { + pushed_mpls_label_stack: [100] + } + } + encap_header { + index: 2 + udp_v6 { + src_ip: "2001:db8::1" + dst_ip: "2001:db8::100" + dst_udp_port: 6635 + ip_ttl: 64 + dscp: 10 + } + } + } + } + +### TE-18.1.2 MPLS-in-UDP IPv6 Traffic Encapsulation + +Send IPv6 packets from ATE port-1 to DUT port-1 with: + +- Source IPv6: 2001:0db8::192:0:2:2 (OTG port-1) +- Destination IPv6: 2001:db8:1:: (matches inner_ipv6_prefix) +- DSCP: 10 +- UDP payload with random source/destination ports + +Validate that ATE port-2 receives MPLS-in-UDP encapsulated packets with: + +**Outer IPv6 Header:** + +- Source IP: 2001:db8::1 +- Destination IP: 2001:db8::100 +- Hop Limit: 64 +- Traffic Class: DSCP 10 + +**UDP Header:** + +- Source Port: random, no need to validate +- Destination Port: 6635 (RFC 7510 standard) +- Protocol: UDP (17) + +**MPLS Header (inside UDP payload):** + +- Label: 100 +- Bottom of Stack: 1 (true) +- TTL: 99 (decremented from inner packet TTL of 100) + +**Inner IPv6 Packet:** + +- Original IPv6 packet with decremented hop limit + +### TE-18.1.3 Traffic Flow Validation + +1. **Positive Test**: Verify traffic flows successfully when + MPLS-in-UDP entries are installed + - Send traffic matching the IPv6 prefix 2001:db8:1::/64 + - Validate 0% packet loss + - Confirm packets are properly encapsulated and forwarded +2. **Negative Test**: Verify traffic fails when MPLS-in-UDP entries are + removed + - Delete the gRIBI entries in reverse order + - Send the same traffic + - Validate 100% packet loss (no forwarding path) + +## Packet Validation Details + +The test performs detailed packet capture validation: + +1. **Capture Analysis**: Uses OTG packet capture on port-2 to analyze + encapsulated packets +2. **Header Parsing**: Validates both gopacket parsing and manual + byte-level parsing of UDP headers +3. **MPLS Validation**: Decodes MPLS header from UDP payload and + validates label, BoS bit, and TTL +4. **Error Detection**: Identifies common issues like incorrect UDP + ports or malformed MPLS headers + +## Expected Behavior + +- **RFC 7510 Compliance**: DUT must use UDP destination port 6635 for + MPLS-in-UDP encapsulation +- **Proper Encapsulation**: Inner IPv6 packets are encapsulated with + MPLS label 100 inside UDP +- **Header Preservation**: DSCP and TTL values are correctly set in + outer headers +- **Forwarding**: Encapsulated packets are properly routed to the egress + port + +## Required DUT platform + +- FFF (supports gRIBI and MPLS-in-UDP encapsulation) + +## OpenConfig Path and RPC Coverage + +``` yaml +paths: + ## Config paths + /network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/sequence-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance: + + ## State paths + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/index: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/type: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/mpls/state/mpls-label-stack: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/src-ip: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/dst-ip: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/src-udp-port: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/dst-udp-port: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/ip-ttl: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/dscp: + /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/state/link-layer-address: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: + gribi: + gRIBI.Modify: + gRIBI.Flush: +``` diff --git a/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/metadata.textproto b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/metadata.textproto new file mode 100644 index 00000000000..2d6abf7c379 --- /dev/null +++ b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/metadata.textproto @@ -0,0 +1,29 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "36cc79b5-3766-4cb4-b83b-1baea1464dc8" +plan_id: "TE-18.1" +description: "gRIBI MPLS-in-UDP Encapsulation" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + static_protocol_name: "STATIC" + gribi_mac_override_static_arp_static_route: true + interface_enabled: true + default_network_instance: "default" + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + static_protocol_name: "STATIC" + gribi_mac_override_static_arp_static_route: true + interface_enabled: true + default_network_instance: "default" + } +} diff --git a/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/mpls_in_udp_test.go b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/mpls_in_udp_test.go new file mode 100644 index 00000000000..0fcce37c469 --- /dev/null +++ b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp/mpls_in_udp_test.go @@ -0,0 +1,885 @@ +// Copyright 2025 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 mpls_in_udp_test implements TE-18.1 MPLS-in-UDP encapsulation tests +package mpls_in_udp_test + +import ( + "context" + "encoding/binary" + "fmt" + "math/rand" + "os" + "slices" + "strings" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gribi" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/gribigo/client" + "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/otg" + "github.com/openconfig/ygot/ygot" +) + +const ( + // Network configuration + ethertypeIPv4 = oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV4 + ethertypeIPv6 = oc.PacketMatchTypes_ETHERTYPE_ETHERTYPE_IPV6 + clusterPolicy = "vrf_selection_policy_c" + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + trafficDuration = 15 * time.Second + seqIDBase = uint32(10) + + // MPLS-in-UDP test configuration + mplsLabel = uint64(100) + outerIPv6Src = "2001:db8::1" + outerIPv6Dst = "2001:db8::100" + outerDstUDPPort = uint16(6635) // RFC 7510 standard MPLS-in-UDP port + outerIPTTL = uint8(64) + outerDscp = uint8(10) + innerIPv6Prefix = "2001:db8:1::/64" + ttl = uint32(100) // Inner packet TTL + + // gRIBI entry IDs for MPLS-in-UDP + mplsNHID = uint64(1001) + mplsNHGID = uint64(2001) + + // gRIBI entry IDs for basic routing infrastructure + basicNHID = uint64(300) + basicNHGID = uint64(400) + + // Static ARP configuration + magicIP = "192.168.1.1" + magicMac = "02:00:00:00:00:01" + + // Test flow configuration + dscpEncapA1 = 10 + + // OTG capture limitation: Cannot start capture on more than one port belonging to the + // same resource group or on more than one port behind the same front panel port + otgMultiPortCaptureSupported = false +) + +var ( + otgDstPorts = []string{"port2"} + otgSrcPort = "port1" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + MAC: "02:01:00:00:00:01", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:f:d:e::1", + IPv6Len: ipv6PrefixLen, + } + + otgPort1 = attrs.Attributes{ + Name: "otgPort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:2", + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:5", + IPv6Len: ipv6PrefixLen, + } + + otgPort2 = attrs.Attributes{ + Name: "otgPort2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + dutPort2DummyIP = attrs.Attributes{ + Desc: "dutPort2", + IPv4Sec: "192.0.2.21", + IPv4LenSec: ipv4PrefixLen, + } + + otgPort2DummyIP = attrs.Attributes{ + Desc: "otgPort2", + IPv4: "192.0.2.22", + IPv4Len: ipv4PrefixLen, + } +) + +// pbrRule defines a policy-based routing rule configuration +type pbrRule struct { + sequence uint32 + etherType oc.NetworkInstance_PolicyForwarding_Policy_Rule_L2_Ethertype_Union + encapVrf string +} + +// packetResult defines the expected packet attributes for validation +type packetResult struct { + mplsLabel uint64 + // NOTE: Source UDP port is not validated since it is random + // udpSrcPort uint16 + udpDstPort uint16 + ipTTL uint8 + srcIP string + dstIP string +} + +// flowAttr defines traffic flow attributes for test packets +type flowAttr struct { + src string // source IP address + dst string // destination IP address + srcPort string // source OTG port + dstPorts []string // destination OTG ports + srcMac string // source MAC address + dstMac string // destination MAC address + topo gosnappi.Config +} + +var ( + // IPv6 flow configuration for MPLS-in-UDP testing + fa6 = flowAttr{ + src: otgPort1.IPv6, + dst: strings.Split(innerIPv6Prefix, "/")[0], // Extract IPv6 prefix for inner destination + srcMac: otgPort1.MAC, + dstMac: dutPort1.MAC, + srcPort: otgSrcPort, + dstPorts: otgDstPorts, + topo: gosnappi.NewConfig(), + } +) + +// testArgs holds the objects needed by a test case +type testArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + topo gosnappi.Config + client *gribi.Client +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// programBasicEntries sets up the basic routing infrastructure needed for MPLS-in-UDP encapsulation. +// This creates the necessary NH/NHG entries to route encapsulated packets to the egress port. +func programBasicEntries(t *testing.T, dut *ondatra.DUTDevice, c *gribi.Client) { + t.Log("Setting up basic routing infrastructure for MPLS-in-UDP") + + // Create basic NH pointing to port2 interface with appropriate MAC configuration + if deviations.GRIBIMACOverrideWithStaticARP(dut) { + t.Logf("Using GRIBIMACOverrideWithStaticARP deviation - adding NH %d with dummy IP", basicNHID) + c.AddNH(t, basicNHID, "MACwithIp", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Dest: otgPort2DummyIP.IPv4, Mac: magicMac}) + } else if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + t.Logf("Using GRIBIMACOverrideStaticARPStaticRoute deviation - adding NH %d with magic IP", basicNHID) + p2 := dut.Port(t, "port2") + nh, op1 := gribi.NHEntry(basicNHID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Interface: p2.Name(), Mac: magicMac, Dest: magicIP}) + nhg, op2 := gribi.NHGEntry(basicNHGID, map[uint64]uint64{basicNHID: 1}, + deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + c.AddEntries(t, []fluent.GRIBIEntry{nh, nhg}, []*client.OpResult{op1, op2}) + } else { + t.Logf("Using default deviation - adding NH %d with interface", basicNHID) + p2 := dut.Port(t, "port2") + c.AddNH(t, basicNHID, "MACwithInterface", deviations.DefaultNetworkInstance(dut), + fluent.InstalledInFIB, &gribi.NHOptions{Interface: p2.Name(), Mac: magicMac}) + + // Create basic NHG for routing to port2 + c.AddNHG(t, basicNHGID, map[uint64]uint64{basicNHID: 1}, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) + } + + // Add IPv6 route for outer destination IP to enable encapsulated packets to reach port2 + // This is essential - without this route, encapsulated packets cannot be forwarded + t.Logf("Adding IPv6 route %s/128 -> NHG %d", outerIPv6Dst, basicNHGID) + c.AddIPv6(t, outerIPv6Dst+"/128", basicNHGID, deviations.DefaultNetworkInstance(dut), + deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) +} + +func TestMPLSOUDPEncap(t *testing.T) { + ctx := context.Background() + + // Configure DUT and ATE + dut := ondatra.DUT(t, "dut") + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + topo := configureOTG(t, ate) + + t.Log("Pushing config to ATE and starting protocols...") + otg.PushConfig(t, topo) + otg.StartProtocols(t) + + // Wait for protocols to initialize + t.Log("Waiting for IPv6 neighbor discovery...") + time.Sleep(30 * time.Second) + otgutils.WaitForARP(t, otg, topo, "IPv6") + + // Configure gRIBI client + c := gribi.Client{ + DUT: dut, + FIBACK: true, + Persistence: true, + } + + if err := c.Start(t); err != nil { + t.Fatalf("gRIBI Connection can not be established") + } + defer c.Close(t) + c.BecomeLeader(t) + + // Flush all existing AFT entries and set up basic routing infrastructure + c.FlushAll(t) + programBasicEntries(t, dut, &c) + + // Verify basic infrastructure is properly installed + if err := c.AwaitTimeout(ctx, t, time.Minute); err != nil { + t.Fatalf("Failed to install basic infrastructure entries: %v", err) + } + + // Define MPLS-in-UDP test case + testCase := struct { + name string + entries []fluent.GRIBIEntry + wantAddResults []*client.OpResult + wantDelResults []*client.OpResult + flows []gosnappi.Flow + capturePorts []string + wantMPLSLabel uint64 + wantOuterDstIP string + wantOuterSrcIP string + wantOuterDstUDPPort uint16 + wantOuterIPTTL uint8 + }{ + name: "MPLS-in-UDP IPv6 Traffic Encap", + entries: []fluent.GRIBIEntry{ + // Create MPLS-in-UDP encapsulation next hop + fluent.NextHopEntry(). + WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithIndex(mplsNHID). + AddEncapHeader( + fluent.MPLSEncapHeader().WithLabels(mplsLabel), + fluent.UDPV6EncapHeader(). + WithSrcIP(outerIPv6Src). + WithDstIP(outerIPv6Dst). + WithDstUDPPort(uint64(outerDstUDPPort)). + WithIPTTL(uint64(outerIPTTL)). + WithDSCP(uint64(outerDscp)), + ), + // Create next hop group pointing to MPLS NH + fluent.NextHopGroupEntry(). + WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithID(mplsNHGID). + AddNextHop(mplsNHID, 1), + // Create IPv6 route that triggers MPLS-in-UDP encapsulation + fluent.IPv6Entry(). + WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). + WithPrefix(innerIPv6Prefix). + WithNextHopGroup(mplsNHGID). + WithNextHopGroupNetworkInstance(deviations.DefaultNetworkInstance(dut)), + }, + wantAddResults: []*client.OpResult{ + fluent.OperationResult(). + WithNextHopOperation(mplsNHID). + WithProgrammingResult(fluent.InstalledInFIB). + WithOperationType(constants.Add). + AsResult(), + fluent.OperationResult(). + WithNextHopGroupOperation(mplsNHGID). + WithProgrammingResult(fluent.InstalledInFIB). + WithOperationType(constants.Add). + AsResult(), + fluent.OperationResult(). + WithIPv6Operation(innerIPv6Prefix). + WithProgrammingResult(fluent.InstalledInFIB). + WithOperationType(constants.Add). + AsResult(), + }, + wantDelResults: []*client.OpResult{ + fluent.OperationResult(). + WithIPv6Operation(innerIPv6Prefix). + WithProgrammingResult(fluent.InstalledInFIB). + WithOperationType(constants.Delete). + AsResult(), + fluent.OperationResult(). + WithNextHopGroupOperation(mplsNHGID). + WithProgrammingResult(fluent.InstalledInFIB). + WithOperationType(constants.Delete). + AsResult(), + fluent.OperationResult(). + WithNextHopOperation(mplsNHID). + WithProgrammingResult(fluent.InstalledInFIB). + WithOperationType(constants.Delete). + AsResult(), + }, + flows: []gosnappi.Flow{fa6.getFlow("ipv6", "ip6mpls", dscpEncapA1)}, + capturePorts: otgDstPorts[:1], + wantMPLSLabel: uint64(mplsLabel), + wantOuterDstIP: outerIPv6Dst, + wantOuterSrcIP: outerIPv6Src, + wantOuterDstUDPPort: outerDstUDPPort, + wantOuterIPTTL: outerIPTTL, + } + + tcArgs := &testArgs{ + client: &c, + dut: dut, + ate: ate, + topo: topo, + } + + t.Run(testCase.name, func(t *testing.T) { + // Add MPLS-in-UDP entries + t.Log("Adding MPLS-in-UDP entries") + c.AddEntries(t, testCase.entries, testCase.wantAddResults) + + // Enable capture and send traffic + expectedPacket := &packetResult{ + mplsLabel: testCase.wantMPLSLabel, + udpDstPort: testCase.wantOuterDstUDPPort, + ipTTL: testCase.wantOuterIPTTL, + srcIP: testCase.wantOuterSrcIP, + dstIP: testCase.wantOuterDstIP, + } + + if otgMultiPortCaptureSupported { + enableCapture(t, ate.OTG(), topo, testCase.capturePorts) + t.Log("Start capture and send traffic") + sendTraffic(t, tcArgs, testCase.flows, true) + t.Log("Validate captured packet attributes") + validateMPLSPacketCapture(t, ate, testCase.capturePorts[0], expectedPacket) + clearCapture(t, ate.OTG(), topo) + } else { + for _, port := range testCase.capturePorts { + enableCapture(t, ate.OTG(), topo, []string{port}) + t.Log("Start capture and send traffic") + sendTraffic(t, tcArgs, testCase.flows, true) + t.Log("Validate captured packet attributes") + validateMPLSPacketCapture(t, ate, port, expectedPacket) + clearCapture(t, ate.OTG(), topo) + } + } + + // Validate traffic flows + t.Log("Validate traffic flows") + validateTrafficFlows(t, tcArgs, testCase.flows, false, true) + + // Clean up MPLS entries + t.Log("Deleting MPLS-in-UDP entries") + slices.Reverse(testCase.entries) + c.DeleteEntries(t, testCase.entries, testCase.wantDelResults) + + // Verify traffic fails after deletion + t.Log("Verify traffic fails after entry deletion") + validateTrafficFlows(t, tcArgs, testCase.flows, false, false) + }) +} + +// getPbrRules returns policy-based routing rules for VRF selection +func getPbrRules(dut *ondatra.DUTDevice) []pbrRule { + vrfDefault := deviations.DefaultNetworkInstance(dut) + + if deviations.PfRequireMatchDefaultRule(dut) { + return []pbrRule{ + { + sequence: 17, + etherType: ethertypeIPv4, + encapVrf: vrfDefault, + }, + { + sequence: 18, + etherType: ethertypeIPv6, + encapVrf: vrfDefault, + }, + } + } + return []pbrRule{ + { + sequence: 17, + encapVrf: vrfDefault, + }, + } +} + +// seqIDOffset returns sequence ID with base offset to ensure proper ordering +func seqIDOffset(dut *ondatra.DUTDevice, i uint32) uint32 { + if deviations.PfRequireSequentialOrderPbrRules(dut) { + return i + seqIDBase + } + return i +} + +// getPbrPolicy creates policy-based routing configuration for VRF selection +func getPbrPolicy(dut *ondatra.DUTDevice, name string) *oc.NetworkInstance_PolicyForwarding { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + pf := ni.GetOrCreatePolicyForwarding() + p := pf.GetOrCreatePolicy(name) + p.SetType(oc.Policy_Type_VRF_SELECTION_POLICY) + + for _, pRule := range getPbrRules(dut) { + r := p.GetOrCreateRule(seqIDOffset(dut, pRule.sequence)) + + if deviations.PfRequireMatchDefaultRule(dut) { + if pRule.etherType != nil { + r.GetOrCreateL2().Ethertype = pRule.etherType + } + } + + if pRule.encapVrf != "" { + r.GetOrCreateAction().SetNetworkInstance(pRule.encapVrf) + } + } + return pf +} + +// configureBaseconfig configures network instances and forwarding policy on the DUT +func configureBaseconfig(t *testing.T, dut *ondatra.DUTDevice) { + fptest.ConfigureDefaultNetworkInstance(t, dut) + pf := getPbrPolicy(dut, clusterPolicy) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), pf) +} + +// staticARPWithMagicUniversalIP configures static ARP with magic universal IP +func staticARPWithMagicUniversalIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + sb := &gnmi.SetBatch{} + p2 := dut.Port(t, "port2") + + s := &oc.NetworkInstance_Protocol_Static{ + Prefix: ygot.String(magicIP + "/32"), + NextHop: map[string]*oc.NetworkInstance_Protocol_Static_NextHop{ + "0": { + Index: ygot.String("0"), + InterfaceRef: &oc.NetworkInstance_Protocol_Static_NextHop_InterfaceRef{ + Interface: ygot.String(p2.Name()), + }, + }, + }, + } + sp := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + gnmi.BatchUpdate(sb, sp.Static(magicIP+"/32").Config(), s) + gnmi.BatchUpdate(sb, gnmi.OC().Interface(p2.Name()).Config(), configStaticArp(p2.Name(), magicIP, magicMac)) + sb.Set(t, dut) +} + +// formatMPLSHeader formats MPLS header bytes for debugging output +func formatMPLSHeader(data []byte) string { + if len(data) < 4 { + return "Invalid MPLS header: too short" + } + + headerValue := binary.BigEndian.Uint32(data[:4]) + label := (headerValue >> 12) & 0xFFFFF + exp := uint8((headerValue >> 9) & 0x07) + s := (headerValue >> 8) & 0x01 + ttl := uint8(headerValue & 0xFF) + + return fmt.Sprintf("MPLS Label: %d, EXP: %d, BoS: %t, TTL: %d", label, exp, s == 1, ttl) +} + +// validateMPLSPacketCapture validates MPLS-in-UDP encapsulated packets from capture +func validateMPLSPacketCapture(t *testing.T, ate *ondatra.ATEDevice, otgPortName string, pr *packetResult) { + t.Logf("=== PACKET CAPTURE VALIDATION START for port %s ===", otgPortName) + + packetBytes := ate.OTG().GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(otgPortName)) + t.Logf("Captured %d bytes from port %s", len(packetBytes), otgPortName) + + if len(packetBytes) == 0 { + t.Errorf("No packet data captured on port %s", otgPortName) + return + } + + // Write capture to temporary pcap file for analysis + f, err := os.CreateTemp("", ".pcap") + if err != nil { + t.Fatalf("Could not create temporary pcap file: %v", err) + } + if _, err := f.Write(packetBytes); err != nil { + t.Fatalf("Could not write packetBytes to pcap file: %v", err) + } + f.Close() + + handle, err := pcap.OpenOffline(f.Name()) + if err != nil { + t.Fatalf("Could not open pcap file: %v", err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + + packetCount := 0 + mplsPacketCount := 0 + validMplsPacketCount := 0 + + for packet := range packetSource.Packets() { + packetCount++ + + // Look for UDP-IPv6 packets (MPLS-in-UDP encapsulation) + udpLayer := packet.Layer(layers.LayerTypeUDP) + ipv6Layer := packet.Layer(layers.LayerTypeIPv6) + if udpLayer == nil || ipv6Layer == nil { + if packetCount < 5 { + t.Logf("Packet %d: Skipping non-UDP-IPv6 packet", packetCount) + } + continue + } + + mplsPacketCount++ + t.Logf("Packet %d: Found UDP-IPv6 packet for validation", packetCount) + packetValid := true + + // Validate IPv6 outer header + v6Packet := ipv6Layer.(*layers.IPv6) + t.Logf("Packet %d: IPv6 src=%s, dst=%s, hopLimit=%d", packetCount, + v6Packet.SrcIP.String(), v6Packet.DstIP.String(), v6Packet.HopLimit) + + if v6Packet.DstIP.String() != pr.dstIP { + t.Errorf("Packet %d: Got outer destination IP %s, want %s", packetCount, v6Packet.DstIP.String(), pr.dstIP) + packetValid = false + } + if v6Packet.SrcIP.String() != pr.srcIP { + t.Errorf("Packet %d: Got outer source IP %s, want %s", packetCount, v6Packet.SrcIP.String(), pr.srcIP) + packetValid = false + } + if v6Packet.HopLimit != pr.ipTTL { + t.Errorf("Packet %d: Got outer hop limit %d, want %d", packetCount, v6Packet.HopLimit, pr.ipTTL) + packetValid = false + } + + // Validate UDP header - extract raw bytes for robust parsing + udpHeaderBytes := udpLayer.LayerContents() + t.Logf("Packet %d: UDP header bytes: %X", packetCount, udpHeaderBytes) + + if len(udpHeaderBytes) < 8 { + t.Errorf("Packet %d: UDP header too short (len: %d)", packetCount, len(udpHeaderBytes)) + packetValid = false + } + + // Validate MPLS header inside UDP payload + payload := udpLayer.LayerPayload() + if len(payload) < 4 { + t.Errorf("Packet %d: UDP payload too short for MPLS header, len=%d", packetCount, len(payload)) + packetValid = false + } else { + mplsHeaderVal := binary.BigEndian.Uint32(payload[:4]) + label := (mplsHeaderVal >> 12) & 0xFFFFF + bottomOfStack := (mplsHeaderVal >> 8) & 0x1 + mplsTTL := mplsHeaderVal & 0xFF + + t.Logf("Packet %d: %s", packetCount, formatMPLSHeader(payload[:4])) + + if uint64(label) != pr.mplsLabel { + t.Errorf("Packet %d: Got MPLS Label %d, want %d", packetCount, label, pr.mplsLabel) + packetValid = false + } + if bottomOfStack != 1 { + t.Errorf("Packet %d: Got MPLS Bottom of Stack bit %d, want 1", packetCount, bottomOfStack) + packetValid = false + } + expectedMPLSTTL := ttl - 1 // Inner packet TTL decremented by 1 + if uint32(mplsTTL) != expectedMPLSTTL { + t.Errorf("Packet %d: Got MPLS TTL %d, want %d", packetCount, mplsTTL, expectedMPLSTTL) + packetValid = false + } + } + + if packetValid { + validMplsPacketCount++ + if validMplsPacketCount <= 2 { + t.Logf("Packet %d: MPLS validation PASSED", packetCount) + } + } else { + t.Logf("Packet %d: MPLS validation FAILED", packetCount) + } + } + + // Summary and validation results + t.Logf("=== PACKET CAPTURE VALIDATION SUMMARY ===") + t.Logf("Total packets captured: %d", packetCount) + t.Logf("UDP-IPv6 packets found: %d", mplsPacketCount) + t.Logf("Valid MPLS-in-UDP packets: %d", validMplsPacketCount) + + if packetCount == 0 { + t.Errorf("No packets captured on port %s", otgPortName) + } else if mplsPacketCount == 0 { + t.Errorf("No UDP-IPv6 packets found in capture on port %s", otgPortName) + } else if validMplsPacketCount == 0 { + t.Errorf("No valid MPLS-in-UDP packets found in capture on port %s", otgPortName) + } else if validMplsPacketCount < (mplsPacketCount / 2) { + t.Errorf("Many packets (%d/%d) failed validation", mplsPacketCount-validMplsPacketCount, mplsPacketCount) + } else { + t.Logf("Packet capture validation PASSED: Found %d valid MPLS-in-UDP packets", validMplsPacketCount) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + p1 := dut.Port(t, "port1") + p2 := dut.Port(t, "port2") + portList := []*ondatra.Port{p1, p2} + dutPortAttrs := []attrs.Attributes{dutPort1, dutPort2} + + // Configure interfaces + for idx, a := range dutPortAttrs { + p := portList[idx] + intf := a.NewOCInterface(p.Name(), dut) + + // Configure 100G ports for specific vendors + if p.PMD() == ondatra.PMD100GBASELR4 && dut.Vendor() != ondatra.CISCO && dut.Vendor() != ondatra.JUNIPER { + e := intf.GetOrCreateEthernet() + if !deviations.AutoNegotiateUnsupported(dut) { + e.AutoNegotiate = ygot.Bool(false) + } + if !deviations.DuplexModeUnsupported(dut) { + e.DuplexMode = oc.Ethernet_DuplexMode_FULL + } + if !deviations.PortSpeedUnsupported(dut) { + e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB + } + } + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), intf) + } + + // Configure base policies and network instances + configureBaseconfig(t, dut) + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + + // Apply policy-based forwarding to source interface + applyForwardingPolicy(t, dut, p1.Name()) + + // Set up static ARP configuration for gRIBI NH entries + if deviations.GRIBIMACOverrideWithStaticARP(dut) { + staticARPWithSecondaryIP(t, dut) + } else if deviations.GRIBIMACOverrideStaticARPStaticRoute(dut) { + staticARPWithMagicUniversalIP(t, dut) + } + + // Allow time for configuration to be applied + time.Sleep(10 * time.Second) +} + +// applyForwardingPolicy applies the VRF selection policy to the ingress interface +func applyForwardingPolicy(t *testing.T, dut *ondatra.DUTDevice, ingressPort string) { + d := &oc.Root{} + interfaceID := ingressPort + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = ingressPort + ".0" + } + + pfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Interface(interfaceID) + pfCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreatePolicyForwarding().GetOrCreateInterface(interfaceID) + pfCfg.ApplyVrfSelectionPolicy = ygot.String(clusterPolicy) + pfCfg.GetOrCreateInterfaceRef().Interface = ygot.String(ingressPort) + pfCfg.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + gnmi.Replace(t, dut, pfPath.Config(), pfCfg) +} + +// configureOTG configures the OTG topology and ports +func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + topo := gosnappi.NewConfig() + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + + otgPort1.AddToOTG(topo, p1, &dutPort1) + otgPort2.AddToOTG(topo, p2, &dutPort2) + + // Configure 100G ports to disable FEC for compatibility + var pmd100GBASELR4 []string + for _, p := range topo.Ports().Items() { + port := ate.Port(t, p.Name()) + if port.PMD() == ondatra.PMD100GBASELR4 { + pmd100GBASELR4 = append(pmd100GBASELR4, port.ID()) + } + } + if len(pmd100GBASELR4) > 0 { + l1Settings := topo.Layer1().Add().SetName("L1").SetPortNames(pmd100GBASELR4) + l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") + autoNegotiate := l1Settings.AutoNegotiation() + autoNegotiate.SetRsFec(false) + } + + return topo +} + +// enableCapture enables packet capture on specified OTG ports +func enableCapture(t *testing.T, otg *otg.OTG, topo gosnappi.Config, otgPortNames []string) { + for _, port := range otgPortNames { + topo.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) + } + otg.PushConfig(t, topo) +} + +// clearCapture clears packet capture from all OTG ports +func clearCapture(t *testing.T, otg *otg.OTG, topo gosnappi.Config) { + topo.Captures().Clear() + otg.PushConfig(t, topo) +} + +func randRange(max int, count int) []uint32 { + rand.New(rand.NewSource(time.Now().UnixNano())) + var result []uint32 + for len(result) < count { + result = append(result, uint32(rand.Intn(max))) + } + return result +} + +// getFlow creates a traffic flow for MPLS-in-UDP testing +func (fa *flowAttr) getFlow(flowType string, name string, dscp uint32) gosnappi.Flow { + flow := fa.topo.Flows().Add().SetName(name) + flow.Metrics().SetEnable(true) + + flow.TxRx().Port().SetTxName(fa.srcPort).SetRxNames(fa.dstPorts) + e1 := flow.Packet().Add().Ethernet() + e1.Src().SetValue(fa.srcMac) + e1.Dst().SetValue(fa.dstMac) + + // For MPLS-in-UDP testing, we only support IPv6 flows + if flowType == "ipv6" { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(fa.src) + v6.Dst().SetValue(fa.dst) + v6.HopLimit().SetValue(ttl) + v6.TrafficClass().SetValue(dscp << 2) + } + + // Add UDP payload to generate traffic + udp := flow.Packet().Add().Udp() + udp.SrcPort().SetValues(randRange(50001, 10000)) + udp.DstPort().SetValues(randRange(50001, 10000)) + + return flow +} + +// sendTraffic sends traffic flows for the specified duration +func sendTraffic(t *testing.T, args *testArgs, flows []gosnappi.Flow, capture bool) { + otg := args.ate.OTG() + args.topo.Flows().Clear().Items() + args.topo.Flows().Append(flows...) + + otg.PushConfig(t, args.topo) + otg.StartProtocols(t) + + otgutils.WaitForARP(t, args.ate.OTG(), args.topo, "IPv4") + otgutils.WaitForARP(t, args.ate.OTG(), args.topo, "IPv6") + + if capture { + startCapture(t, args.ate) + defer stopCapture(t, args.ate) + } + + otg.StartTraffic(t) + time.Sleep(trafficDuration) + otg.StopTraffic(t) +} + +// validateTrafficFlows verifies traffic flow behavior (pass/fail) based on expected outcome +func validateTrafficFlows(t *testing.T, args *testArgs, flows []gosnappi.Flow, capture bool, match bool) { + t.Logf("=== TRAFFIC FLOW VALIDATION START (expecting match=%v) ===", match) + + otg := args.ate.OTG() + sendTraffic(t, args, flows, capture) + + otgutils.LogPortMetrics(t, otg, args.topo) + otgutils.LogFlowMetrics(t, otg, args.topo) + + for _, flow := range flows { + outPkts := float32(gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State())) + inPkts := float32(gnmi.Get(t, otg, gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State())) + lossPct := ((outPkts - inPkts) * 100) / outPkts + + t.Logf("Flow %s: OutPkts=%v, InPkts=%v, LossPct=%v", flow.Name(), outPkts, inPkts, lossPct) + + if outPkts == 0 { + t.Fatalf("OutPkts for flow %s is 0, want > 0", flow.Name()) + } + + if match { + // Expecting traffic to pass (0% loss) + if got := lossPct; got > 0 { + t.Fatalf("Traffic validation FAILED: Flow %s has %v%% packet loss, want 0%%", flow.Name(), got) + } else { + t.Logf("Traffic validation PASSED: Flow %s has 0%% packet loss", flow.Name()) + } + } else { + // Expecting traffic to fail (100% loss) + if got := lossPct; got != 100 { + t.Fatalf("Traffic validation FAILED: Flow %s has %v%% packet loss, want 100%%", flow.Name(), got) + } else { + t.Logf("Traffic validation PASSED: Flow %s has 100%% packet loss", flow.Name()) + } + } + } +} + +// startCapture starts packet capture on OTG ports +func startCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) +} + +// stopCapture stops packet capture on OTG ports +func stopCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, cs) +} + +// configStaticArp configures static ARP entries for gRIBI next hop resolution +func configStaticArp(p string, ipv4addr string, macAddr string) *oc.Interface { + i := &oc.Interface{Name: ygot.String(p)} + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + s := i.GetOrCreateSubinterface(0) + s4 := s.GetOrCreateIpv4() + n4 := s4.GetOrCreateNeighbor(ipv4addr) + n4.LinkLayerAddress = ygot.String(macAddr) + return i +} + +// staticARPWithSecondaryIP configures secondary IPs and static ARP for gRIBI compatibility +func staticARPWithSecondaryIP(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + p2 := dut.Port(t, "port2") + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), dutPort2DummyIP.NewOCInterface(p2.Name(), dut)) + gnmi.Update(t, dut, gnmi.OC().Interface(p2.Name()).Config(), configStaticArp(p2.Name(), otgPort2DummyIP.IPv4, magicMac)) +} diff --git a/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp_scale/README.md b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp_scale/README.md new file mode 100644 index 00000000000..b2644b85792 --- /dev/null +++ b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp_scale/README.md @@ -0,0 +1,488 @@ +# TE-18.3: MPLS in UDP Encapsulation Scale Test + +Building on TE-18.1 and TE-18.2, this test focuses on scaling +gRIBI-programmed MPLS-over-UDP tunnels and associated forwarding +entries, parameterized by key scaling dimensions. + +## Topology + +**Physical Topology:** + +- 4 physical ports total (2 DUT ports + 2 ATE ports) + - 2 ports as ingress interfaces (port1-port2) + - 2 ports as egress/uplink interfaces (port3-port4) + +**Logical Interface Scale Design:** + +- 32 logical ingress interfaces achieved through: + - 16 VLAN subinterfaces per physical ingress port (2 ports × 16 VLANs = + 32 logical interfaces) + - VLAN IDs: 100-115 on port1, 200-215 on port2 +- Multiple VRFs mapped to logical interfaces as required by scale + profiles +- Each logical interface assigned to appropriate VRF based on test + profile requirements + + + + ATE port-1 <------> port-1 DUT (VLANs 100-115) + ATE port-2 <------> port-2 DUT (VLANs 200-215) + DUT port-3 <------> port-3 ATE (Egress) + DUT port-4 <------> port-4 ATE (Egress) + +- 32 logical interfaces as the ‘input port set’ (Ingress) +- 2 ports as “uplink facing” (Egress) +- Network Instances (VRFs) will be mapped from ingress + ports/subinterfaces as needed by scale profiles. + +## Test Setup + +### DUT Configuration + +1. **Physical Interface Configuration:** + + - Configure ports 1-4 with IPv6 addressing using base scheme + 2001:f:d:e::/126 network + - Enable all physical interfaces with PMD100GBASEFR-specific + settings + - Apply ethernet configuration: AutoNegotiate=false, + DuplexMode=FULL, PortSpeed=100GB + - Set MAC addresses using systematic scheme: 02:01:00:00:00:XX for + DUT ports + +2. **VLAN Subinterface Configuration:** + + - Create 16 VLAN subinterfaces per ingress port (32 total logical + interfaces) + - Assign IPv6 addresses using 2001:f:d:e::/126 base with systematic + increments + - Configure subinterface-to-VRF mappings based on test profile + requirements + - Enable IPv4 protocols on subinterfaces when + deviations.InterfaceEnabled(dut) is required + +3. **VRF Configuration:** + + - Create required VRFs based on test profile: + - Profile 1: DEFAULT network instance only + - Profiles 2-3: 1024 VRFs (VRF_001 through VRF_1024) plus DEFAULT + - Profile 4: DEFAULT network instance only + - Profile 5: DEFAULT network instance only + - Use device-specific default network instance naming conventions + - Apply policy-based forwarding rules for VRF selection using + DSCP/source IP criteria + +4. **Static Routes and Forwarding:** + + - Configure static routes using device-specific static protocol + naming + - Set up IPv6 static routes with next-hop pointing to ATE port IPv6 + addresses + - Use standard static route protocol type configuration + - Configure routes in appropriate network instances based on test + profile requirements + +### ATE Configuration + +1. **Physical Port Setup:** + + - Configure 4 physical ports with IPv6 addresses matching DUT + interface scheme + - Use MAC addresses: 02:00:XX:01:01:01 pattern for ATE ports + - Set up VLAN tagging on ingress ports (port1-2) to match DUT + subinterface VLANs + - Configure egress ports (port3-4) for traffic reception and + MPLS-in-UDP validation + - Apply PMD100GBASEFR-specific settings: disable FEC, set speed to + 100Gbps, enable auto-negotiate + +2. **Traffic Generation:** + + - Create traffic flows targeting the 20,000 unique destination + prefixes + - Use IPv6 flow destination base: 2015:aa8:: as defined in Test + Parameters + - Distribute traffic across 32 logical ingress interfaces using VLAN + tags + - Configure flows with appropriate DSCP markings for VRF selection + - Set traffic duration: 15 seconds as defined in Test Parameters + +3. **Packet Capture and Validation:** + + - Enable packet capture on egress ports for MPLS-in-UDP + encapsulation validation + - Configure capture filters for MPLS label stack and UDP + encapsulation verification + - Validate outer IPv6 headers: source 2001:f:a:1::0, destination + 2001:f:c:e::1 as defined in Test Parameters + - Verify UDP destination port 6635 as defined in Test Parameters + - Check outer DSCP marking: 26 and TTL: 64 as defined in Test + Parameters + +### gRIBI Programming Setup + +1. **Client Configuration:** + + - Establish gRIBI client connection with RIB_AND_FIB_ACK: true and + Persistence: true + - Use standard gRIBI client configuration pattern + - Make sure the client is the leader + - Set appropriate batch sizes and operation rates per profile + requirements + +2. **Entry Programming Sequence:** + + - Program Next Hop (NH) entries with MPLS-in-UDP encapsulation + headers + - Use NH ID starting from 201 as defined in Test Parameters + - Create Next Hop Groups (NHGs) starting from ID 10 as defined in + Test Parameters + - Install IPv6 prefix entries using 2015:aa8::/128 base prefix + pattern as defined in Test Parameters + - Validate FIB_PROGRAMMED status for all programmed entries + +3. **Scale-Specific Configurations:** + + - Profile 1: DEFAULT network instance with 20,000 NHGs, 1 NH per + NHG, 1 MPLS label + - Profiles 2-3: 1024 VRFs with distributed NHGs/prefixes, unique + MPLS labels per VRF + - Profile 4: DEFAULT network instance with 2,500 NHGs, 8 NHs per NHG + (ECMP), 1 MPLS label + - Profile 5: High-rate programming (1,000 ops/sec) with 50% ADD/50% + DELETE operations + +4. **Device-Specific Considerations:** + + - Handle vendor-specific gRIBI encapsulation header support + limitations + - Use CLI configuration for tunnel encapsulation when gRIBI encap + headers unsupported + - Apply device-specific interface enablement requirements for IPv4 + protocols + - Configure tunnel type: "mpls-over-udp udp destination port 6635" + as defined in Test Parameters + +### Test Parameters + +**DUT Interface IPv6 Addressing:** + +- dut_port_base_ipv6 = “2001:f:d:e::/126” +- dut_port1_ipv6 = “2001:f:d:e::1/126” +- dut_port2_ipv6 = “2001:f:d:e::5/126” +- dut_port3_ipv6 = “2001:f:d:e::9/126” +- dut_port4_ipv6 = “2001:f:d:e::13/126” + +**ATE Interface IPv6 Addressing:** + +- ate_port1_ipv6 = “2001:f:d:e::2/126” +- ate_port2_ipv6 = “2001:f:d:e::6/126” +- ate_port3_ipv6 = “2001:f:d:e::10/126” +- ate_port4_ipv6 = “2001:f:d:e::14/126” + +**MAC Address Schemes:** + +- dut_mac_pattern = “02:01:00:00:00:XX” +- ate_mac_pattern = “02:00:XX:01:01:01” + +**Inner IPv6 Destinations:** + +- inner_ipv6_dst_A = “2001:aa:bb::1/128” +- inner_ipv6_dst_B = “2001:aa:bb::2/128” + +**Inner IPv4 Destinations:** + +- ipv4_inner_dst_A = “10.5.1.1/32” +- ipv4_inner_dst_B = “10.5.1.2/32” + +**Outer IPv6 Encapsulation:** + +- outer_ipv6_src = “2001:f:a:1::0” +- outer_ipv6_dst_A = “2001:f:c:e::1” +- outer_ipv6_dst_B = “2001:f:c:e::2” +- outer_ipv6_dst_def = “2001:1:1:1::0” +- outer_dst_udp_port = “5555” +- outer_dscp = “26” +- outer_ip_ttl = “64” + +**Traffic Flow Parameters:** + +- ipv6_flow_base = “2015:aa8::” +- ipv6_prefix_base = “2015:aa8::/128” + +**Traffic Parameters:** + +- traffic_duration = “15 seconds” +- target_packet_loss = “≤ 1%” + +**gRIBI Parameters:** + +- nh_id_start = “201” +- nhg_id_start = “10” + +## Procedure + +### TE-18.3 Overview: Scaling Dimensions and Targets + +This test evaluates scaling across the following dimensions using gRIBI. +The test profiles below represent different parameter combinations of +these dimensions. + +- **Network Instances (VRFs):** Number of separate routing instances. +- **Next Hop Groups (NHGs):** Total number of NHGs programmed. Target: + **Up to 20,000** (profile-dependent). +- **Next Hops (NHs):** Total number of NHs programmed. **Constraint: + Maximum 20,000 total NHs.** When there are more NHs per NHG, there + will be fewer total NHGs (e.g., 2,500 NHGs if each NHG has 8 NHs). +- **NHs per NHG:** Number of NH entries within each NHG (e.g., 1 or 8). +- **Prefixes:** Total number of unique IPv4/IPv6 exact-match forwarding + entries (routes) across all VRFs. Target: **20,000 total**. +- **(Unique Destination IP + MPLS) Tuples:** The combination of the + inner destination IP and the MPLS label used in the NH encapsulation. + Target: **Up to 20,000 unique tuples**. +- **MPLS Labels:** Number and uniqueness of MPLS labels used in NH + encapsulation. **Constraint:** The number of unique MPLS labels must + equal the number of VRFs (#MPLS Labels == \#VRFs). +- **gRIBI Operations Rate (QPS):** Rate of gRIBI Modify requests or + operations per second. +- **gRIBI Batch Size:** Number of AFT entries (or operations) per + ModifyRequest. +- **Convergence:** DUT packet forwarding updated within **1 second** + after receiving FIB_PROGRAMMED acknowledgement for added entries + (baseline). +- **IP Address Reuse:** Inner IP destination prefixes should be reused + across different Network Instances where applicable. +- **Multi-VRF Distribution:** In multi-VRF profiles, both NHGs and + prefixes are distributed across the different VRFs as specified in + each profile. + +### TE-18.3: Scale Profiles + +#### Profile 1 (Single VRF) + +- **Goal:** Baseline single VRF scale (Exact Label Match scenario). +- **Network Instances (VRFs):** 1 (DEFAULT). +- **Total NHGs:** 20,000. +- **NHs per NHG:** 1. +- **MPLS Labels:** 1 (consistent with \#VRFs = 1). Same label used for + all NHs. +- **Total Prefixes:** 20,000 (e.g., 10k IPv4, 10k IPv6). +- **Unique (Dest IP + MPLS) Tuples:** 20,000 (different destination IPs, + same MPLS label). +- **Prefix Mapping:** 1 unique prefix -\> 1 unique NHG (1:1). +- **Total NHs:** 20,000 (20,000 NHGs × 1 NH/NHG = 20,000 total NHs). +- **gRIBI Rate/Batch:** Baseline (e.g., 1 ModifyRequest/sec, 200 + entries/request) - QPS not the primary focus here. + +#### Profile 2 (Multi-VRF) + +- **Goal:** Scale across multiple VRFs with unique labels per VRF. +- **Network Instances (VRFs):** 1024. +- **Total NHGs:** 20,000 (distributed across VRFs, ~19-20 NHGs/VRF). +- **NHs per NHG:** 1. +- **Total NHs:** 20,000 (20,000 NHGs × 1 NH/NHG = 20,000 total NHs). +- **MPLS Labels:** 1024 unique labels (1 label assigned per VRF, + consistent with \#VRFs = 1024). +- **Total Prefixes:** 20,000 (distributed across VRFs, ~19-20 + prefixes/VRF). +- **Unique (Dest IP + MPLS) Tuples:** 20,000 (e.g., 20 unique + destination IPs reused per MPLS label/VRF). +- **Prefix Mapping:** Prefixes within a VRF map to NHGs using that VRF’s + unique MPLS label. +- **Inner IP Reuse:** Required. +- **gRIBI Rate/Batch:** Baseline - QPS not the primary focus here. + +#### Profile 3 (Multi-VRF) + +- **Goal:** Similar to Profile 2, but test potentially skewed + distribution of prefixes/routes per VRF/label. +- **Network Instances (VRFs):** 1024. +- **Total NHGs:** 20,000. +- **NHs per NHG:** 1. +- **Total NHs:** 20,000 (20,000 NHGs × 1 NH/NHG = 20,000 total NHs). +- **MPLS Labels:** 1024 unique labels (1 per VRF). +- **Total Prefixes:** 20,000. +- **Unique (Dest IP + MPLS) Tuples:** 20,000. +- **Prefix Mapping:** Similar to Profile 2, but the distribution of the + 20k prefixes across the 1024 VRFs/labels might be intentionally uneven + (e.g., some VRFs have many more prefixes than others). *Exact skew + pattern TBD.* +- **Inner IP Reuse:** Required. +- **gRIBI Rate/Batch:** Baseline - QPS not the primary focus here. + +#### Profile 4 (Single VRF) + +- **Goal:** Test ECMP scale within a single VRF. +- **Network Instances (VRFs):** 1 (DEFAULT). +- **Total NHGs:** 2,500. +- **NHs per NHG:** 8 (each NH having a different destination IP). +- **Total NHs:** 20,000 (2,500 NHGs × 8 NHs/NHG = 20,000 total NHs, + respecting the 20k NH constraint). +- **MPLS Labels:** 1 (consistent with \#VRFs = 1). Same label used for + all NHs. +- **Total Prefixes:** 20,000 (e.g., 10k IPv4, 10k IPv6). +- **Unique (Dest IP + MPLS) Tuples:** 20,000 (different destination IPs + across all NHs, same MPLS label). +- **Prefix Mapping:** 8 unique prefixes -\> 1 unique NHG (8:1 mapping, + repeated 2500 times). +- **gRIBI Rate/Batch:** Baseline - QPS not the primary focus here. + +#### Profile 5 (Single VRF) + +- **Goal:** Test gRIBI control plane QPS scaling and impact on + dataplane. Uses Profile 1 as the base state. + +- **Network Instances (VRFs):** 1 (DEFAULT). + +- **Total NHGs:** 20,000. + +- **NHs per NHG:** 1. + +- **MPLS Labels:** 1. + +- **Total Prefixes:** 20,000. + +- **Unique (Dest IP + MPLS) Tuples:** 20,000. + +- **Prefix Mapping:** 1:1. + +- **Total NHs:** 20,000 (20,000 NHGs × 1 NH/NHG = 20,000 total NHs). + +- **gRIBI Operations:** Program/Modify the full 20k entries (1 Prefix + + 1 NHG + 1 NH = 3 operations per entry = 60k operations total). + + - Target Rate: **1,000 operations/second** (aiming to update the full + table in maximum of 60 seconds). + - Operation Mix: Test with **50% ADD, 50% DELETE** operations during + high-rate phase. + +- **Dataplane Validation:** Ensure live traffic forwarding remains + stable and correct during high-rate gRIBI operations. The primary + success criterion is zero packet loss during the update phase. This + validates that the DUT correctly implements a “make-before-break” + update sequence, where traffic for a modified prefix is seamlessly + forwarded using either the old or the new state, without being + dropped. + +### TE-18.3.3 Validation Procedures + +#### Procedure - Single VRF Validation (Profiles 1, 4) + +- Program all gRIBI entries (NHs, NHGs, Prefixes) according to the + profile using baseline rate/batch. +- Validate `FIB_PROGRAMMED` status is received from DUT for all entries. +- Verify AFT state on DUT for a sample of entries (NH, NHG, Prefix -\> + NHG mapping). +- Send traffic matching programmed prefixes from appropriate ingress + ports. +- Verify traffic is received on egress ports with correct MPLS-over-UDP + encapsulation (correct outer IPs, UDP port, MPLS label). +- Measure packet loss (target: \<= 1% steady state). +- Delete all gRIBI entries. +- Verify AFT state shows entries removed. +- Verify traffic loss is 100%. + +#### Procedure - Multi-VRF Validation (Profiles 2, 3) + +- Program all gRIBI entries across all specified VRFs according to the + profile using baseline rate/batch. +- Validate `FIB_PROGRAMMED` status for all entries. +- Verify AFT state on DUT for a sample of entries within different VRFs. +- Send traffic matching programmed prefixes, ensuring traffic is + directed to the correct VRF (e.g., via appropriate ingress interface + mapping). +- Verify traffic is received with correct MPLS-over-UDP encapsulation, + including the VRF-specific MPLS label. +- Measure packet loss (target: \<= 1% steady state). +- Delete all gRIBI entries. +- Verify AFT state shows entries removed across VRFs. +- Verify traffic loss is 100%. + +#### Procedure - ECMP Validation (Profile 4) + +- Perform Single VRF Validation steps. +- Additionally, verify that traffic sent towards prefixes mapped to the + ECMP NHG is distributed across the multiple NHs within that NHG + (requires ATE support for flow analysis or DUT counter validation for + NH packet/octet counters). + +#### Procedure - gRIBI Rate Validation (Profile 5) + +- Establish the baseline state (e.g., program 20k entries as per Profile + 1). + +- Start traffic flows matching the programmed entries. Verify baseline + forwarding and low loss. + +- Initiate high-rate gRIBI Modify operations (e.g., 100 + ModifyRequests/sec, 10 ops/request, 50% ADD/50% DELETE mix targeting + existing/new entries). + +- Monitor gRIBI operation results (ACKs) for success/failure and + latency. + +- Continuously monitor traffic forwarding during the high-rate gRIBI + phase. + + - Verify traffic uses correct encapsulation based on the programmed + state. + - Measure packet loss (target: minimal loss, allowing for brief + transient loss during updates, but stable low loss overall). + +- Validate `FIB_PROGRAMMED` status is received promptly for updates. + +- Verify AFT state on DUT reflects the changes made during the high-rate + phase. + +- Stop high-rate programming and measure steady-state loss again. + +#### Investigation - VRF Impact on QPS + +- As an extension, investigate if the number of VRFs impacts gRIBI QPS + or dataplane stability during high-rate updates. This could involve + running a variation of Profile 5 using the multi-VRF setup from + Profile 2 or 3 as the baseline state. + +#### OpenConfig Path and RPC Coverage + +```yaml +paths: + # AFTs Next-Hop state (Verification) + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/index: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/state/type: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/mpls/state/mpls-label-stack: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/src-ip: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/dst-ip: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/src-udp-port: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/dst-udp-port: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/ip-ttl: + /network-instances/network-instance/afts/next-hops/next-hop/encap-headers/encap-header/udp-v6/state/dscp: + /network-instances/network-instance/afts/next-hops/next-hop/state/counters/packets-forwarded: + /network-instances/network-instance/afts/next-hops/next-hop/state/counters/octets-forwarded: + /network-instances/network-instance/afts/next-hops/next-hop/state/ip-address: # NH IP + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/id: + /network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/index: + /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/state/link-layer-address: + + # AFTs Prefix Entry state (Verification) + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + # Primarily used for verification (Subscribe/Get) + gNMI.Subscribe: + on_change: true + gNMI.Get: + gribi: + # Used for programming all AFT entries + gRIBI.Modify: + gRIBI.Flush: +``` + +## Required DUT platform + +- FFF diff --git a/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp_scale/metadata.textproto b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp_scale/metadata.textproto new file mode 100644 index 00000000000..49e842f474b --- /dev/null +++ b/feature/gribi/mpls_in_udp/otg_tests/mpls_in_udp_scale/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "fce08e5f-1fba-49cb-beee-f72478ffb5f3" +plan_id: "TE-18.3" +description: "MPLS in UDP Encapsulation Scale Test" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/backup_nhg_multiple_nh_pbf_test.go b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/backup_nhg_multiple_nh_pbf_test.go index d095f255be6..a28e43d3f9a 100644 --- a/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/backup_nhg_multiple_nh_pbf_test.go +++ b/feature/gribi/otg_tests/backup_nhg_multiple_nh_pbf_test/backup_nhg_multiple_nh_pbf_test.go @@ -376,7 +376,7 @@ func (a *testArgs) createFlow(t *testing.T, name, dstMac string) string { e1.Dst().SetValue(dstMac) v4 := flow.Packet().Add().Ipv4() v4.Src().Increment().SetStart(decapFlowSrc) - v4.Priority().Dscp().Phb().SetValues([]uint32{dscpEncapA1}) + v4.Priority().Dscp().Phb().SetValue(dscpEncapA1) v4.Dst().Increment().SetStart(dstPfxMin).SetCount(routeCount) // use ip over ip packets since some vendors only support decap for backup diff --git a/feature/gribi/otg_tests/base_hierarchical_nhg_update/base_hierarchical_nhg_update_test.go b/feature/gribi/otg_tests/base_hierarchical_nhg_update/base_hierarchical_nhg_update_test.go index a3e5d790bb0..43157969e14 100644 --- a/feature/gribi/otg_tests/base_hierarchical_nhg_update/base_hierarchical_nhg_update_test.go +++ b/feature/gribi/otg_tests/base_hierarchical_nhg_update/base_hierarchical_nhg_update_test.go @@ -280,11 +280,13 @@ type transitKey struct{} // testBaseHierarchialNHGwithVrfPolW verifies recursive IPv4 Entry for // 198.51.100.0/24 (a) with vrf selection w func testBaseHierarchialNHGwithVrfPolW(ctx context.Context, t *testing.T, args *testArgs) { - + configureDUT(t, args.dut) // Remove interface from VRF-1. gnmi.Delete(t, args.dut, gnmi.OC().NetworkInstance(vrfName).Config()) p1 := args.dut.Port(t, "port1") - gnmi.Update(t, args.dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), args.dut)) + if !deviations.GRIBIMACOverrideWithStaticARP(args.dut) { + gnmi.Update(t, args.dut, gnmi.OC().Interface(p1.Name()).Config(), dutPort1.NewOCInterface(p1.Name(), args.dut)) + } if deviations.ExplicitInterfaceInDefaultVRF(args.dut) { fptest.AssignToNetworkInstance(t, args.dut, p1.Name(), deviations.DefaultNetworkInstance(args.dut), 0) } @@ -854,6 +856,8 @@ func testImplementDrain(ctx context.Context, t *testing.T, args *testArgs) { t.Log("Validate traffic switching from ate port4 back to ate port2 and ate port3") validateTrafficFlows(t, args.ate, nil, []gosnappi.Flow{p4Flow}, []gosnappi.Flow{p2Flow, p3Flow}, switchTrafficToPort2AndPort3FromPort4, args.client, true) + t.Log("Unconfig interfaces after Drain test") + defer deleteDrainConfig(t, args.dut) } @@ -876,6 +880,30 @@ func deleteinterfaceconfig(t *testing.T, dut *ondatra.DUTDevice) { } } +// deleteDrainConfig unconfigs interfaces after drain test +func deleteDrainConfig(t *testing.T, dut *ondatra.DUTDevice) { + + p2 := dut.Port(t, "port2") + p3 := dut.Port(t, "port3") + p4 := dut.Port(t, "port4") + + i2 := &oc.Interface{Name: ygot.String(btrunk2)} + i3 := &oc.Interface{Name: ygot.String(btrunk3)} + i4 := &oc.Interface{Name: ygot.String(btrunk4)} + + gnmi.Delete(t, dut, gnmi.OC().Interface(btrunk2).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(btrunk3).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(btrunk4).Config()) + + gnmi.Delete(t, dut, gnmi.OC().Interface(p2.Name()).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(p3.Name()).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(p4.Name()).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(*i2.Name).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(*i3.Name).Config()) + gnmi.Delete(t, dut, gnmi.OC().Interface(*i4.Name).Config()) + +} + // configDUTDrain configures ports for drain test. func configDUTDrain(t *testing.T, dut *ondatra.DUTDevice) { d := gnmi.OC() diff --git a/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go b/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go index dae6ba87148..0e10c8e271a 100644 --- a/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go +++ b/feature/gribi/otg_tests/basic_encap_test/basic_encap_test.go @@ -347,9 +347,11 @@ func TestBasicEncap(t *testing.T) { } defer c.Close(t) + // flush all AFT entries after test + defer c.FlushAll(t) c.BecomeLeader(t) - // Flush all existing AFT entries on the router + // flush all existing AFT entries on the router c.FlushAll(t) programEntries(t, dut, &c) diff --git a/feature/gribi/otg_tests/gribi_route_test/metadata.textproto b/feature/gribi/otg_tests/gribi_route_test/metadata.textproto index cab25b4742d..c57b18e8c8c 100644 --- a/feature/gribi/otg_tests/gribi_route_test/metadata.textproto +++ b/feature/gribi/otg_tests/gribi_route_test/metadata.textproto @@ -50,3 +50,4 @@ platform_exceptions: { explicit_interface_in_default_vrf: false } } +tags: TAGS_TRANSIT diff --git a/feature/gribi/otg_tests/mpls_in_udp/metadata.textproto b/feature/gribi/otg_tests/mpls_in_udp/metadata.textproto deleted file mode 100644 index eb147c526e1..00000000000 --- a/feature/gribi/otg_tests/mpls_in_udp/metadata.textproto +++ /dev/null @@ -1,31 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto -# proto-message: Metadata - -uuid: "36cc79b5-3766-4cb4-b83b-1baea1464dc8" -plan_id: "TE-18.1" -description: "gRIBI MPLS in UDP Encapsulation" -testbed: TESTBED_DUT_ATE_4LINKS -platform_exceptions: { - platform: { - vendor: ARISTA - } - deviations: { - static_protocol_name: "STATIC" - gribi_encap_header_unsupported: true - gribi_mac_override_static_arp_static_route: true - interface_enabled: true - default_network_instance: "default" - } -} -platform_exceptions: { - platform: { - vendor: CISCO - } - deviations: { - static_protocol_name: "STATIC" - gribi_encap_header_unsupported: true - gribi_mac_override_static_arp_static_route: true - interface_enabled: true - default_network_instance: "default" - } -} diff --git a/feature/gribi/otg_tests/mpls_in_udp/mpls_in_udp_test.go b/feature/gribi/otg_tests/mpls_in_udp/mpls_in_udp_test.go deleted file mode 100644 index 46fa4205265..00000000000 --- a/feature/gribi/otg_tests/mpls_in_udp/mpls_in_udp_test.go +++ /dev/null @@ -1,381 +0,0 @@ -// 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 mpls_in_udp_test - -import ( - "fmt" - "strconv" - "testing" - "time" - - "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/featureprofiles/internal/attrs" - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/featureprofiles/internal/fptest" - "github.com/openconfig/featureprofiles/internal/gribi" - "github.com/openconfig/featureprofiles/internal/helpers" - "github.com/openconfig/featureprofiles/internal/otgutils" - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/otg" - "github.com/openconfig/ygot/ygot" -) - -const ( - ipv6PrefixLen = 126 - ipv6FlowIP = "2015:aa8::1" - trafficDuration = 15 * time.Second - nhg10ID = 10 - vrfEncapA = "ENCAP_TE_VRF_A" - ipv6EntryPrefix = "2015:aa8::" - ipv6EntryPrefixLen = 128 - nh201ID = 201 - nhgName = "nh-group-1" - outerIpv6Src = "2001:f:a:1::0" - outerIpv6DstA = "2001:f:c:e::1" - outerDstUDPPort = "6635" - outerDscp = "26" - outerIPTTL = "64" -) - -var ( - otgDstPorts = []string{"port2"} - otgSrcPort = "port1" - dutPort1 = attrs.Attributes{ - Desc: "dutPort1", - MAC: "02:01:00:00:00:01", - IPv6: "2001:f:d:e::1", - IPv6Len: ipv6PrefixLen, - } - otgPort1 = attrs.Attributes{ - Name: "otgPort1", - MAC: "02:00:01:01:01:01", - IPv6: "2001:f:d:e::2", - IPv6Len: ipv6PrefixLen, - } - dutPort2 = attrs.Attributes{ - Desc: "dutPort2", - MAC: "02:01:00:00:00:02", - IPv6: "2001:f:d:e::5", - IPv6Len: ipv6PrefixLen, - } - otgPort2 = attrs.Attributes{ - Name: "otgPort2", - MAC: "02:00:02:01:01:01", - IPv6: "2001:f:d:e::6", - IPv6Len: ipv6PrefixLen, - } - fa6 = flowAttr{ - src: otgPort1.IPv6, - dst: outerIpv6DstA, - defaultDst: ipv6FlowIP, - srcMac: otgPort1.MAC, - dstMac: dutPort1.MAC, - srcPort: otgSrcPort, - dstPorts: otgDstPorts, - topo: gosnappi.NewConfig(), - } -) - -type flowAttr struct { - src string // source IP address - dst string // destination IP address - defaultDst string // default destination IP address - srcPort string // source OTG port - dstPorts []string // destination OTG ports - srcMac string // source MAC address - dstMac string // destination MAC address - topo gosnappi.Config -} - -// testArgs holds the objects needed by a test case. -type testArgs struct { - dut *ondatra.DUTDevice - ate *ondatra.ATEDevice - topo gosnappi.Config - client *gribi.Client -} - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { - d := gnmi.OC() - p1 := dut.Port(t, "port1") - p2 := dut.Port(t, "port2") - portList := []*ondatra.Port{p1, p2} - - // configure interfaces - for idx, a := range []attrs.Attributes{dutPort1, dutPort2} { - p := portList[idx] - intf := a.NewOCInterface(p.Name(), dut) - if p.PMD() == ondatra.PMD100GBASEFR && dut.Vendor() != ondatra.CISCO && dut.Vendor() != ondatra.JUNIPER { - e := intf.GetOrCreateEthernet() - e.AutoNegotiate = ygot.Bool(false) - e.DuplexMode = oc.Ethernet_DuplexMode_FULL - e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB - } - if deviations.InterfaceEnabled(dut) { - s := intf.GetOrCreateSubinterface(0) - s4 := s.GetOrCreateIpv4() - s4.Enabled = ygot.Bool(true) - } - - gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), intf) - } -} - -// configureOTG configures port1 on the OTG. -func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { - otg := ate.OTG() - topo := gosnappi.NewConfig() - t.Logf("Configuring OTG port1") - p1 := ate.Port(t, "port1") - p2 := ate.Port(t, "port2") - - otgPort1.AddToOTG(topo, p1, &dutPort1) - otgPort2.AddToOTG(topo, p2, &dutPort2) - - pmd100GFRPorts := []string{} - for _, p := range topo.Ports().Items() { - port := ate.Port(t, p.Name()) - if port.PMD() == ondatra.PMD100GBASEFR { - pmd100GFRPorts = append(pmd100GFRPorts, port.ID()) - } - } - // Disable FEC for 100G-FR ports because Novus does not support it. - if len(pmd100GFRPorts) > 0 { - l1Settings := topo.Layer1().Add().SetName("L1").SetPortNames(pmd100GFRPorts) - l1Settings.SetAutoNegotiate(true).SetIeeeMediaDefaults(false).SetSpeed("speed_100_gbps") - autoNegotiate := l1Settings.AutoNegotiation() - autoNegotiate.SetRsFec(false) - } - - t.Logf("Pushing config to ATE and starting protocols...") - otg.PushConfig(t, topo) - t.Logf("starting protocols...") - otg.StartProtocols(t) - time.Sleep(50 * time.Second) - otgutils.WaitForARP(t, ate.OTG(), topo, "IPv6") - return topo -} - -// getFlow returns a flow of ipv6. -func (fa *flowAttr) getFlow(flowType string, name string) gosnappi.Flow { - flow := fa.topo.Flows().Add().SetName(name) - flow.Metrics().SetEnable(true) - - flow.TxRx().Port().SetTxName(fa.srcPort).SetRxNames(fa.dstPorts) - e1 := flow.Packet().Add().Ethernet() - e1.Src().SetValue(fa.srcMac) - e1.Dst().SetValue(fa.dstMac) - if flowType == "ipv6" { - v6 := flow.Packet().Add().Ipv6() - v6.Src().SetValue(fa.src) - switch name { - case "ip6a1": - v6.Dst().SetValue(fa.dst) - case "ip6a2": - v6.Dst().SetValue(fa.defaultDst) - default: - v6.Dst().SetValue(fa.dst) - } - } - return flow -} - -// programEntries pushes RIB entries on the DUT required for Encap functionality -func programEntries(t *testing.T, dut *ondatra.DUTDevice, c *gribi.Client) { - t.Log("Programming RIB entries") - // TODO: vvardhanreddy revisit when functionality is added. - // nh7, op9 := gribi.NHEntry(nh201ID, "EncapUDP", vrfEncapA, fluent.InstalledInFIB, - // &gribi.NHOptions{Src: outerIpv6Src, Dest: outerIpv6DstA, VrfName: vrfEncapA}) - // nhg4, op11 := gribi.NHGEntry(nhg10ID, map[uint64]uint64{nh201ID: 1}, - // vrfEncapA, fluent.InstalledInFIB) - // c.AddEntries(t, []fluent.GRIBIEntry{nh7, nhg4}, []*client.OpResult{op9, op11}) - // c.AddIPv6(t, cidr(ipv6EntryPrefix, ipv6EntryPrefixLen), nhg10ID, vrfEncapA, deviations.DefaultNetworkInstance(dut), fluent.InstalledInFIB) -} - -// configDefaultRoute configures a static route in DEFAULT network-instance. -func configDefaultRoute(t *testing.T, dut *ondatra.DUTDevice, v6Prefix, v6NextHop string) { - t.Logf("TE-18.1.2: Configuring static route in DEFAULT network-instance") - ni := oc.NetworkInstance{Name: ygot.String(deviations.DefaultNetworkInstance(dut))} - static := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) - sr := static.GetOrCreateStatic(v6Prefix) - nh := sr.GetOrCreateNextHop("0") - nh.NextHop = oc.UnionString(v6NextHop) - gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) -} - -// cidr takes as input the IP address and the Mask and returns the IP string in -// CIDR notation. -func cidr(ipaddrs string, ones int) string { - return ipaddrs + "/" + strconv.Itoa(ones) -} - -// configureEncapHeadersCli is only used if a DUT does not support gRIBI. -func configureEncapHeaderCli(t *testing.T, dut *ondatra.DUTDevice) { - switch dut.Vendor() { - case ondatra.ARISTA: - var encapHeaderCLI string - encapHeaderCLI = fmt.Sprintf("tunnel type mpls-over-udp udp destination port %s\n", outerDstUDPPort) - helpers.GnmiCLIConfig(t, dut, encapHeaderCLI) - encapHeaderCLI = fmt.Sprintf("nexthop-group %s type mpls-over-udp\n", nhgName) - encapHeaderCLI += fmt.Sprintf(" tos %s\n", outerDscp) - encapHeaderCLI += fmt.Sprintf(" ttl %s\n", outerIPTTL) - encapHeaderCLI += " fec hierarchical\n" - encapHeaderCLI += fmt.Sprintf(" tunnel-source %s\n", outerIpv6Src) - encapHeaderCLI += fmt.Sprintf(" entry 0 push label-stack 899999 tunnel-destination %s\n", outerIpv6DstA) - helpers.GnmiCLIConfig(t, dut, encapHeaderCLI) - encapHeaderCLI = fmt.Sprintf("ip route vrf customer %s nexthop-group nhg%d\n", outerIpv6DstA, nhg10ID) - helpers.GnmiCLIConfig(t, dut, encapHeaderCLI) - default: - t.Logf("Unsupported vendor %s for native command support for deviation 'GribiEncapHeaderUnsupported'", dut.Vendor()) - } -} - -// Tests TE-18.1.1, TE-18.1.2. TE-18.1.2 was also added due to following reasons: -// TE-18.1.2 describes sending traffic flow that does not match any AFT nexthop -// so try match using already configured TE-18.1.1 gRIBI rules but -// make sure to catch on the default route. -func TestMPLSOUDPEncap(t *testing.T) { - // Configure DUT - dut := ondatra.DUT(t, "dut") - configureDUT(t, dut) - - // Configure ATE - otg := ondatra.ATE(t, "ate") - topo := configureOTG(t, otg) - - // configure gRIBI client - c := gribi.Client{ - DUT: dut, - FIBACK: true, - Persistence: true, - } - - if err := c.Start(t); err != nil { - t.Fatalf("gRIBI Connection can not be established") - } - - defer c.Close(t) - c.BecomeLeader(t) - // Flush all existing AFT entries on the router - c.FlushAll(t) - if deviations.GribiEncapHeaderUnsupported(dut) { - configureEncapHeaderCli(t, dut) - } else { - programEntries(t, dut, &c) - } - configDefaultRoute(t, dut, cidr(ipv6EntryPrefix, ipv6EntryPrefixLen), otgPort1.IPv6) - test := []struct { - name string - flows []gosnappi.Flow - capturePorts []string - }{ - { - name: "TE-18.1.1 Match and Encapsulate using gRIBI aft modify", - flows: []gosnappi.Flow{fa6.getFlow("ipv6", "ip6a1")}, - capturePorts: otgDstPorts, - }, - { - name: "TE-18.1.2 Validate prefix match rule for MPLS in GRE encap using default route", - flows: []gosnappi.Flow{fa6.getFlow("ipv6", "ip6a2")}, - capturePorts: otgDstPorts, - }, - } - - tcArgs := &testArgs{ - client: &c, - dut: dut, - ate: otg, - topo: topo, - } - - for _, tc := range test { - t.Run(tc.name, func(t *testing.T) { - t.Logf("Name: %s", tc.name) - enableCapture(t, otg.OTG(), topo, tc.capturePorts) - t.Log("Start capture and send traffic") - sendTraffic(t, tcArgs, tc.flows, true) - t.Log("Validate captured packet attributes") - // TODO: b/364961777 upstream GUE decoder to gopacket addition is pending. - // err := validatePacketCapture(t, tcArgs, tc.capturePorts) - clearCapture(t, otg.OTG(), topo) - // if err != nil { - // t.Fatalf("Failed to validate ATE port 2 receives MPLS-IN-UDP packets: %v", err) - // } - }) - } -} - -// clearCapture clears capture from all ports on the OTG -func clearCapture(t *testing.T, otg *otg.OTG, topo gosnappi.Config) { - t.Log("Clearing capture") - topo.Captures().Clear() - otg.PushConfig(t, topo) -} - -// sendTraffic starts traffic flows and send traffic for a fixed duration -func sendTraffic(t *testing.T, args *testArgs, flows []gosnappi.Flow, capture bool) { - otg := args.ate.OTG() - args.topo.Flows().Clear().Items() - args.topo.Flows().Append(flows...) - - otg.PushConfig(t, args.topo) - otg.StartProtocols(t) - - otgutils.WaitForARP(t, args.ate.OTG(), args.topo, "IPv4") - otgutils.WaitForARP(t, args.ate.OTG(), args.topo, "IPv6") - - if capture { - startCapture(t, args.ate) - defer stopCapture(t, args.ate) - } - t.Log("Starting traffic") - otg.StartTraffic(t) - time.Sleep(trafficDuration) - otg.StopTraffic(t) - t.Log("Traffic stopped") -} - -// startCapture starts the capture on the otg ports -func startCapture(t *testing.T, ate *ondatra.ATEDevice) { - otg := ate.OTG() - cs := gosnappi.NewControlState() - cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) - otg.SetControlState(t, cs) -} - -// enableCapture enables packet capture on specified list of ports on OTG -func enableCapture(t *testing.T, otg *otg.OTG, topo gosnappi.Config, otgPortNames []string) { - for _, port := range otgPortNames { - t.Log("Enabling capture on ", port) - topo.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) - } - pb, _ := topo.Marshal().ToProto() - t.Log(pb.GetCaptures()) - otg.PushConfig(t, topo) -} - -// stopCapture starts the capture on the otg ports -func stopCapture(t *testing.T, ate *ondatra.ATEDevice) { - otg := ate.OTG() - cs := gosnappi.NewControlState() - cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) - otg.SetControlState(t, cs) -} diff --git a/feature/gribi/otg_tests/mpls_in_udp_scale/README.md b/feature/gribi/otg_tests/mpls_in_udp_scale/README.md deleted file mode 100644 index e2f42b8c481..00000000000 --- a/feature/gribi/otg_tests/mpls_in_udp_scale/README.md +++ /dev/null @@ -1,145 +0,0 @@ -# TE-18.3 MPLS in UDP Encapsulation with QoS Scheduler Scale Test - -Building on TE-18.1 and TE-18.2, add scaling parameters - -## Topology - -* 32 ports as the 'input port set' -* 4 ports as "uplink facing" -* VLAN configurations - * input vlans are distributed evenly across the 'input port set' - -## Test setup - -TODO: Complete test environment setup steps - -inner_ipv6_dst_A = "2001:aa:bb::1/128" -inner_ipv6_dst_B = "2001:aa:bb::2/128" -inner_ipv6_default = "::/0" - -ipv4_inner_dst_A = "10.5.1.1/32" -ipv4_inner_dst_B = "10.5.1.2/32" -ipv4_inner_default = "0.0.0.0/0" - -outer_ipv6_src = "2001:f:a:1::0" -outer_ipv6_dst_A = "2001:f:c:e::1" -outer_ipv6_dst_B = "2001:f:c:e::2" -outer_ipv6_dst_def = "2001:1:1:1::0" -outer_dst_udp_port = "5555" -outer_dscp = "26" -outer_ip-ttl = "64" - -## Procedure - -### TE-18.3.1 Scale - -#### Scale targets - -* Flow scale - * 20,000 IPv4/IPv6 destinations - * 1,000 vlans - * Inner IP address space should be reused for each network-instance. - * gRIBI client update rate `flow_r` = 1 update per second - * Each gRIBI update include ip entries in batches of `flow_q` = 200 - * DUT packet forwarding updated within 1 second after adding entries - -* Scheduler (policer) scale - * 1,000 policer rates - * 20,000 policer-policies / token buckets instantiations - * Update policer-policies at 1 per `sched_r` = 60 seconds - * Update policer-policies in a batch of `sched_q` = 1,000 - * Policer-policies changes should take effect within `sched_r` / 2 time - -#### Scale profile A - many vlans - -* 20 ip destinations * 1,000 vlans = 20,000 'flows' -* Each ingress vlan has 20 policer-policies = 10,000 'token buckets' -* The 20 ip destinations are split evenly between the 20 policers -* Each policer is assigned rate limits matching one of 800 different possible limits between 1Gbps to 400Gbps in 0.5Gbps increments - -#### Scale profile B - many destinations, few vlans - -* 200 ip destinations * 100 vlans = 20,000 'flows' -* Each ingress vlan has 4 policer-policies = 4,000 'token buckets' -* The 200 ip destinations are split evenly between the 4 policers -* Each policer is assigned rate limits matching one of 800 different possible limits between 1Gbps to 400Gbps in 0.5Gbps increments - -#### Procedure - Flow Scale - -* For each scale profile, create the following subsets TE-18.1.5.n - * Configure ATE flows to send 100 pps per flow and wait for ARP - * Send traffic for q flows (destination IP prefixes) for 2 seconds - * At traffic start time, gRIBI client to send `flow_q` aft entries and their - related NHG and NH at rate `flow_r` - * Validate RIB_AND_FIB_ACK with FIB_PROGRAMMED is received from DUT within - 1 second - * Measure packet loss. Target packet loss <= 50%. - * Repeat adding 200 flows until 20,000 flows have been added - * Once reaching 20,000 flows, perform 1 iteration of modifying the first - `flow_q` flows to use different NH,NHG - -#### Procedure - Policer + Flow Scale - -* For each scale profile, create the following subsets TE-18.1.6.n - * Program all 20,000 flows - * Every `sched_r` interval use gnmi.Set to replace `sched_q` scheduler policies - * Verify packet loss changes for all flows within `sched_r` / 2 time - -#### OpenConfig Path and RPC Coverage - -```yaml -paths: - # qos scheduler config - /qos/scheduler-policies/scheduler-policy/config/name: - /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/type: - /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/one-rate-two-color/config/cir: - /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/one-rate-two-color/config/bc: - /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/one-rate-two-color/config/queuing-behavior: - /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/one-rate-two-color/exceed-action/config/drop: - - # qos classifier config - /qos/classifiers/classifier/config/name: - /qos/classifiers/classifier/terms/term/config/id: - #/qos/classifiers/classifier/terms/term/conditions/next-hop-group/config/name: # TODO: new OC leaf to be added - - # qos input-policies config - TODO: a new OC subtree (/qos/input-policies) - # /qos/input-policies/input-policy/config/name: - # /qos/input-policies/input-policy/config/classifier: - # /qos/input-policies/input-policy/config/scheduler-policy: - - # qos interface config - #/qos/interfaces/interface/subinterface/input/config/policies: # TODO: new OC leaf-list (/qos/interfaces/interface/input/config/policies) - - # qos interface scheduler counters - /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-pkts: - /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-octets: - /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-pkts: - /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-octets: - - # afts next-hop counters - /network-instances/network-instance/afts/next-hops/next-hop/state/counters/packets-forwarded: - /network-instances/network-instance/afts/next-hops/next-hop/state/counters/octets-forwarded: - - # afts state paths set via gRIBI - # TODO: https://github.com/openconfig/public/pull/1153 - #/network-instances/network-instance/afts/next-hops/next-hop/mpls-in-udp/state/src-ip: - #/network-instances/network-instance/afts/next-hops/next-hop/mpls-in-udp/state/dst-ip: - #/network-instances/network-instance/afts/next-hops/next-hop/mpls-in-udp/state/ip-ttl: - #/network-instances/network-instance/afts/next-hops/next-hop/mpls-in-udp/state/dst-udp-port: - #/network-instances/network-instance/afts/next-hops/next-hop/mpls-in-udp/state/dscp: - -rpcs: - gnmi: - gNMI.Set: - union_replace: true - replace: true - gNMI.Subscribe: - on_change: true - gribi: - gRIBI.Modify: - gRIBI.Flush: -``` - -## Required DUT platform - -* FFF diff --git a/feature/gribi/otg_tests/route_addition_during_failover_test/README.md b/feature/gribi/otg_tests/route_addition_during_failover_test/README.md index c2e05fd3e6c..fa174820098 100644 --- a/feature/gribi/otg_tests/route_addition_during_failover_test/README.md +++ b/feature/gribi/otg_tests/route_addition_during_failover_test/README.md @@ -49,6 +49,11 @@ rpcs: gRIBI.Flush: ``` +## Canonical OC +```json +{} +``` + ## Config parameter coverage ## Telemery parameter coverage diff --git a/feature/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go b/feature/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go index 69d9cc787cf..784500d9594 100644 --- a/feature/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go +++ b/feature/gribi/otg_tests/route_addition_during_failover_test/route_addition_during_failover_test.go @@ -673,7 +673,7 @@ func TestRouteAdditionDuringFailover(t *testing.T) { t.Log("Reconnect gRIBi client after switchover on new master.") - retryDuration := 180 * time.Second + retryDuration := 320 * time.Second retryInterval := 5 * time.Second startTime := time.Now() diff --git a/feature/gribi/otg_tests/route_leakage_between_nondefault_vrfs/README.md b/feature/gribi/otg_tests/route_leakage_between_nondefault_vrfs/README.md deleted file mode 100644 index 53ae4040a27..00000000000 --- a/feature/gribi/otg_tests/route_leakage_between_nondefault_vrfs/README.md +++ /dev/null @@ -1,319 +0,0 @@ -# TE-6.3: Route Leakage between Non Default VRF - -## Summary - -Validate that Route leakage from VRF-1 to VRF-2 is possible and is able to -. - -## Testbed type - -* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) - -## Topology: - -```mermaid -graph LR; -A[ATE PORT1] <--IPv4-IPv6--> B[DUT:PORT1]; -C[ATE:PORT2] <--IPv4-IPv6--> D[DUT:PORT2]; -``` - -## Procedure - -### Initial setup - -* Connect ATE port-1 to DUT port-1, and ATE port-2 to DUT port-2. -* Configure 192.1.1.1/30 to DUT port-1 and 192.1.1.2/30 to ATE port-1 and - Configure 192.1.1.4/30 to DUT port-2 and 192.1.1.5/30 to ATE port-2 -* Create a non-default VRF (VRF-1) that includes DUT port-1 and create - another non-default VRF (VRF-2) that includes DUT port-2. -* Configure eBGP from ATE port-1 (AS 65001) to DUT port-1 (AS 65003) and also - from ATE port-2 (AS 65003) to DUT port-2 (AS 65002) -* Advertise 50.1.1.0/24 route from ATE-port1 using the eBGP session. Advertise - 60.1.1.0/24 route from ATE-port2 using the eBGP session. -* Configure a bi-directional traffic flow with following parameters - Src: 50.1.1.0/24 & Dst: 60.1.1.0/24 and vice-versa - Traffic: PPS of 10,000 packets per second, Frame size of 256 bytes - -### TE-6.3.1 - -* Start the protocols from ATE-port1 and ATE-port2 -* Verify that 50.1.1.0/24 is installed in VRF-1 routing-instance and - 60.1.1.0/24 is installed in VRF-2 routing-instance. -* Start the traffic from ATE-port1 and ATE-port2 -* Validate that there is 100% traffic loss seen as the VRF-1 routes are - reachable by VRF-2 routes and vice-versa. - -### TE-6.3.2 -* Leak the route 50.1.1.0/24 from VRF-1 routing-instance of ATE-port1 to - VRF-2 routing-instance. -* Leak the route 60.1.1.0/24 from VRF-2 routing-instance of ATE-port2 to - VRF-1 routing-instance. -* Start the protocols from ATE-port1 and ATE-port2 -* Verify that 50.1.1.0/24 is installed in VRF-1 routing-instance and - 60.1.1.0/24 is installed in VRF-2 routing-instance. -* Start the traffic from ATE-port1 and ATE-port2 -* Validate that traffic is flowing in both directions with 0% traffic loss. - -## Canonical OC Configuration -```json -/network-instances/network-instance[name=VRF-1]/protocols/protocol[identifier=BGP][name=BGP]/bgp: - { - "openconfig-network-instance:global": { - "afi-safis": { - "afi-safi": [ - { - "add-paths": { - "config": { - "receive": false, - "send": false - } - }, - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", - "enabled": true - }, - }, - ] - }, - "config": { - "as": 65001, - "router-id": "192.1.1.1" - }, - "route-selection-options": { - "config": { - "always-compare-med": false, - "external-compare-router-id": true, - "ignore-as-path-length": false, - "ignore-next-hop-igp-metric": false - } - }, - }, - "openconfig-network-instance:neighbors": { - "neighbor": [ - { - "afi-safis": { - "afi-safi": [ - "add-paths": { - "config": { - "receive": true, - "send": true - } - }, - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "enabled": true - }, - "ipv6-unicast": { - "config": { - "send-default-route": false - }, - "prefix-limit": { - "config": { - "prevent-teardown": false - } - } - }, - ] - }, - "apply-policy": { - "config": { - "default-export-policy": "REJECT_ROUTE", - "default-import-policy": "REJECT_ROUTE" - } - }, - "config": { - "enabled": true, - "neighbor-address": "1000::50.1.1.2", - "peer-as": 64502, - "peer-group": "BGP-PEER-GROUP-V6", - "route-flap-damping": false, - "send-community": "NONE" - }, - }, - { - "afi-safis": { - "afi-safi": [ - "add-paths": { - "config": { - "receive": false, - "send": false - } - }, - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "enabled": true - }, - "ipv4-unicast": { - "config": { - "send-default-route": false - }, - "prefix-limit": { - "config": { - "prevent-teardown": false - } - } - }, - ] - }, - "apply-policy": { - "config": { - "default-export-policy": "REJECT_ROUTE", - "default-import-policy": "REJECT_ROUTE" - } - }, - "config": { - "enabled": true, - "neighbor-address": "1000::50.1.1.2", - "peer-group": "BGP-PEER-GROUP-V6", - "route-flap-damping": false, - "send-community": "NONE" - }, - }, - ] - } - } -/network-instances/network-instance[name=VRF-2]/protocols/protocol[identifier=BGP][name=BGP]/bgp: - { - "openconfig-network-instance:global": { - "afi-safis": { - "afi-safi": [ - { - "add-paths": { - "config": { - "receive": false, - "send": false - } - }, - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV4_UNICAST", - "enabled": true - }, - }, - ] - }, - "config": { - "as": 65001, - "router-id": "192.1.1.1" - }, - "route-selection-options": { - "config": { - "always-compare-med": false, - "external-compare-router-id": true, - "ignore-as-path-length": false, - "ignore-next-hop-igp-metric": false - } - }, - }, - "openconfig-network-instance:neighbors": { - "neighbor": [ - { - "afi-safis": { - "afi-safi": [ - "add-paths": { - "config": { - "receive": true, - "send": true - } - }, - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "enabled": true - }, - "ipv6-unicast": { - "config": { - "send-default-route": false - }, - "prefix-limit": { - "config": { - "prevent-teardown": false - } - } - }, - ] - }, - "apply-policy": { - "config": { - "default-export-policy": "REJECT_ROUTE", - "default-import-policy": "REJECT_ROUTE" - } - }, - "config": { - "enabled": true, - "neighbor-address": "1000::50.1.1.2", - "peer-as": 64502, - "peer-group": "BGP-PEER-GROUP-V6", - "route-flap-damping": false, - "send-community": "NONE" - }, - }, - { - "afi-safis": { - "afi-safi": [ - "add-paths": { - "config": { - "receive": false, - "send": false - } - }, - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "config": { - "afi-safi-name": "openconfig-bgp-types:IPV6_UNICAST", - "enabled": true - }, - "ipv4-unicast": { - "config": { - "send-default-route": false - }, - "prefix-limit": { - "config": { - "prevent-teardown": false - } - } - }, - ] - }, - "apply-policy": { - "config": { - "default-export-policy": "REJECT_ROUTE", - "default-import-policy": "REJECT_ROUTE" - } - }, - "config": { - "enabled": true, - "neighbor-address": "1000::50.1.1.2", - "peer-group": "BGP-PEER-GROUP-V6", - "route-flap-damping": false, - "send-community": "NONE" - }, - }, - ] - } - } -``` - -## OpenConfig Path and RPC Coverage - -```yaml -rpcs: - gnmi: - gNMI.Get: - gNMI.Set: - gNMI.Subscribe: - -paths: - /network-instances/network-instance/config/name: - /network-instances/network-instance/config/description: - - /network-instances/network-instance/state/name: - /network-instances/network-instance/state/description: -``` - -## Minimum DUT platform requirement - -FFF - diff --git a/feature/gribi/otg_tests/route_removal_during_failover_test/README.md b/feature/gribi/otg_tests/route_removal_during_failover_test/README.md index da9a134e0d0..71ad0edfd58 100644 --- a/feature/gribi/otg_tests/route_removal_during_failover_test/README.md +++ b/feature/gribi/otg_tests/route_removal_during_failover_test/README.md @@ -43,6 +43,11 @@ rpcs: gRIBI.Flush: ``` +## Canonical OC +```json +{} +``` + ## Config parameter coverage ## Telemery parameter coverage diff --git a/feature/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go b/feature/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go index 57851159ad2..a3510b5addf 100644 --- a/feature/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go +++ b/feature/gribi/otg_tests/route_removal_during_failover_test/route_removal_during_failover_test.go @@ -633,7 +633,7 @@ func TestRouteRemovalDuringFailover(t *testing.T) { t.Log("Reconnect gRIBi client after switchover on new master.") - retryDuration := 180 * time.Second + retryDuration := 320 * time.Second retryInterval := 5 * time.Second startTime := time.Now() for { diff --git a/feature/gribi/otg_tests/supervisor_failure_test/README.md b/feature/gribi/otg_tests/supervisor_failure_test/README.md index 53f8bd30b2e..e3d02db92f6 100644 --- a/feature/gribi/otg_tests/supervisor_failure_test/README.md +++ b/feature/gribi/otg_tests/supervisor_failure_test/README.md @@ -64,3 +64,9 @@ rpcs: ## Minimum DUT Required vRX - Virtual Router Device + + +## Canonical OC +```json +{} +``` 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 9799b319ab0..5aac4a61981 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 @@ -362,7 +362,7 @@ func TestSupFailure(t *testing.T) { // Retry starting the gribi client in a loop as switchover may reset the connection. t.Log("Re-establish gRIBI client connection") - retryDuration := 180 * time.Second + retryDuration := 320 * time.Second retryInterval := 5 * time.Second startTime := time.Now() for { diff --git a/feature/gribi/otg_tests/vrf_policy_driven_te/README.md b/feature/gribi/otg_tests/vrf_policy_driven_te/README.md index 7fa0f46bec3..b83ceed8913 100644 --- a/feature/gribi/otg_tests/vrf_policy_driven_te/README.md +++ b/feature/gribi/otg_tests/vrf_policy_driven_te/README.md @@ -37,6 +37,11 @@ DUT port-8 <------> port-8 ATE * magic_mac = 02:00:00:00:00:01` ``` +## Canonical OC +```json +{} +``` + vrf_selection_policy_c ``` network-instances { @@ -866,4 +871,4 @@ rpcs: gribi: gRIBI.Modify: gRIBI.Flush: -``` \ No newline at end of file +``` diff --git a/feature/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto b/feature/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto index 46f58f6f528..430672252a2 100644 --- a/feature/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto +++ b/feature/gribi/otg_tests/vrf_policy_driven_te/metadata.textproto @@ -23,7 +23,6 @@ platform_exceptions: { } deviations: { isis_level_enabled: true - gribi_decap_mixed_plen_unsupported: true } } platform_exceptions: { diff --git a/feature/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go b/feature/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go index 7e2f8f665ff..c77b93f7128 100644 --- a/feature/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go +++ b/feature/gribi/otg_tests/vrf_policy_driven_te/vrf_policy_driven_te_test.go @@ -17,12 +17,12 @@ package vrf_policy_driven_te_test import ( "context" "fmt" - "log" "os" "strconv" "testing" "time" + log "github.com/golang/glog" "github.com/google/gopacket" "github.com/google/gopacket/layers" "github.com/google/gopacket/pcap" @@ -60,72 +60,76 @@ func TestMain(m *testing.M) { // DUT port-8 <------> port-8 ATE const ( - plenIPv4 = 30 - plenIPv6 = 126 - maskLen24 = "24" - maskLen32 = "32" - maskLen126 = "126" - dscpEncapA1 = 10 - dscpEncapA2 = 18 - dscpEncapB1 = 20 - dscpEncapB2 = 28 - dscpEncapNoMatch = 30 - ipv4OuterSrc111WithMask = "198.51.100.111/32" - ipv4OuterSrc222WithMask = "198.51.100.222/32" - magicIp = "192.168.1.1" - magicMac = "02:00:00:00:00:01" - gribiIPv4EntryDefVRF1 = "192.0.2.101" - gribiIPv4EntryDefVRF2 = "192.0.2.102" - gribiIPv4EntryDefVRF3 = "192.0.2.103" - gribiIPv4EntryDefVRF4 = "192.0.2.104" - gribiIPv4EntryDefVRF5 = "192.0.2.105" - gribiIPv4EntryVRF1111 = "203.0.113.1" - gribiIPv4EntryVRF1112 = "203.10.113.2" - gribiIPv4EntryVRF2221 = "203.0.113.100" - gribiIPv4EntryVRF2222 = "203.0.113.101" - gribiIPv4EntryEncapVRF = "138.0.11.0" - gribiIPv6EntryEncapVRF = "2001:db8::138:0:11:0" - ipv4OuterDst111 = "192.51.100.64" - ipv4OuterSrc111 = "198.51.100.111" - ipv4OuterSrc222 = "198.51.100.222" - ipv4OuterSrc333 = "198.100.200.123" - prot4 = 4 - prot41 = 41 - vrfPolW = "vrf_selection_policy_w" - vrfPolC = "vrf_selection_policy_c" - nhIndex = 1 - nhgIndex = 1 - niDecapTeVrf = "DECAP_TE_VRF" - niEncapTeVrfA = "ENCAP_TE_VRF_A" - niEncapTeVrfB = "ENCAP_TE_VRF_B" - niTeVrf111 = "TE_VRF_111" - niTeVrf222 = "TE_VRF_222" - niDefault = "DEFAULT" - tolerancePct = 2 - flowNegTest = "flowNegTest" - ipv4InnerDst = "138.0.11.8" - ipv6InnerDst = "2001:db8::138:0:11:8" - ipv4InnerDstNoEncap = "20.0.0.1" - ipv6InnerDstNoEncap = "2001:db8::20:0:0:1" - ipv4InnerDst2 = "138.0.11.15" - ipv6InnerDst2 = "2001:db8::138:0:11:15" - defaultRoute = "0.0.0.0/0" - wantLoss = true - routeDelete = true - correspondingTTL = 64 - correspondingHopLimit = 64 - flow6in4 = "flow6in4" - flow4in4 = "flow4in4" - v4Flow = true - 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" - seqIDBase = uint32(10) + plenIPv4 = 30 + plenIPv6 = 126 + maskLen24 = "24" + maskLen32 = "32" + maskLen126 = "126" + dscpEncapA1 = 10 + dscpEncapA2 = 18 + dscpEncapB1 = 20 + dscpEncapB2 = 28 + dscpEncapNoMatch = 30 + ecnNotCapable = 0 + ecnCapable1 = 1 + ecnCapable2 = 2 + ecnCongestionExperienced = 3 + ipv4OuterSrc111WithMask = "198.51.100.111/32" + ipv4OuterSrc222WithMask = "198.51.100.222/32" + magicIp = "192.168.1.1" + magicMac = "02:00:00:00:00:01" + gribiIPv4EntryDefVRF1 = "192.0.2.101" + gribiIPv4EntryDefVRF2 = "192.0.2.102" + gribiIPv4EntryDefVRF3 = "192.0.2.103" + gribiIPv4EntryDefVRF4 = "192.0.2.104" + gribiIPv4EntryDefVRF5 = "192.0.2.105" + gribiIPv4EntryVRF1111 = "203.0.113.1" + gribiIPv4EntryVRF1112 = "203.10.113.2" + gribiIPv4EntryVRF2221 = "203.0.113.100" + gribiIPv4EntryVRF2222 = "203.0.113.101" + gribiIPv4EntryEncapVRF = "138.0.11.0" + gribiIPv6EntryEncapVRF = "2001:db8::138:0:11:0" + ipv4OuterDst111 = "192.51.100.64" + ipv4OuterSrc111 = "198.51.100.111" + ipv4OuterSrc222 = "198.51.100.222" + ipv4OuterSrc333 = "198.100.200.123" + prot4 = 4 + prot41 = 41 + vrfPolW = "vrf_selection_policy_w" + vrfPolC = "vrf_selection_policy_c" + nhIndex = 1 + nhgIndex = 1 + niDecapTeVrf = "DECAP_TE_VRF" + niEncapTeVrfA = "ENCAP_TE_VRF_A" + niEncapTeVrfB = "ENCAP_TE_VRF_B" + niTeVrf111 = "TE_VRF_111" + niTeVrf222 = "TE_VRF_222" + niDefault = "DEFAULT" + tolerancePct = 2 + flowNegTest = "flowNegTest" + ipv4InnerDst = "138.0.11.8" + ipv6InnerDst = "2001:db8::138:0:11:8" + ipv4InnerDstNoEncap = "20.0.0.1" + ipv6InnerDstNoEncap = "2001:db8::20:0:0:1" + ipv4InnerDst2 = "138.0.11.15" + ipv6InnerDst2 = "2001:db8::138:0:11:15" + defaultRoute = "0.0.0.0/0" + wantLoss = true + routeDelete = true + correspondingTTL = 64 + correspondingHopLimit = 64 + flow6in4 = "flow6in4" + flow4in4 = "flow4in4" + v4Flow = true + 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" + seqIDBase = uint32(10) ) var ( @@ -363,6 +367,34 @@ type policyFwRule struct { ni string } +func trafficClassFieldsToDecimal(dscpValue, ecnValue uint32) uint32 { + dscpByte := byte(dscpValue) + ecnByte := byte(ecnValue) + tosStr := fmt.Sprintf("%06b%02b", dscpByte, ecnByte) + tosDec, _ := strconv.ParseInt(tosStr, 2, 64) + return uint32(tosDec) +} + +func buildTrafficClassForIpInIp(flowValues *flowArgs) (uint32, uint32) { + innerDscp := uint32(0) + innerEcn := uint32(0) + if len(flowValues.inHdrDscp) != 0 { + innerDscp = flowValues.inHdrDscp[0] + } + if len(flowValues.inHdrEcn) != 0 { + innerEcn = flowValues.inHdrEcn[0] + } + outerDscp := uint32(0) + outerEcn := uint32(0) + if len(flowValues.outHdrDscp) != 0 { + outerDscp = flowValues.outHdrDscp[0] + } + if len(flowValues.outHdrEcn) != 0 { + outerEcn = flowValues.outHdrEcn[0] + } + return trafficClassFieldsToDecimal(innerDscp, innerEcn), trafficClassFieldsToDecimal(outerDscp, outerEcn) +} + func configureVrfSelectionPolicyW(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() d := &oc.Root{} @@ -804,11 +836,12 @@ func verifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntf string) { }).Await(t) if !ok { t.Logf("IS-IS state on %v has no adjacencies", dutIntf) - t.Fatal("No IS-IS adjacencies reported.") + t.Fatal("No IS-IS adjacencies reported") } } func createFlow(flowValues *flowArgs) gosnappi.Flow { + innerTrafficClass, outerTrafficClass := buildTrafficClassForIpInIp(flowValues) flow := gosnappi.NewFlow().SetName(flowValues.flowName) flow.Metrics().SetEnable(true) flow.TxRx().Device().SetTxNames([]string{"atePort1.IPv4"}) @@ -822,9 +855,7 @@ func createFlow(flowValues *flowArgs) gosnappi.Flow { outerIpHdr := flow.Packet().Add().Ipv4() outerIpHdr.Src().SetValue(flowValues.outHdrSrcIP) outerIpHdr.Dst().SetValue(flowValues.outHdrDstIP) - if len(flowValues.outHdrDscp) != 0 { - outerIpHdr.Priority().Dscp().Phb().SetValues(flowValues.outHdrDscp) - } + outerIpHdr.Priority().Raw().SetValue(outerTrafficClass) if flowValues.udp { UDPHeader := flow.Packet().Add().Udp() UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) @@ -841,11 +872,7 @@ func createFlow(flowValues *flowArgs) gosnappi.Flow { innerIpHdr := flow.Packet().Add().Ipv4() innerIpHdr.Src().SetValue(flowValues.InnHdrSrcIP) innerIpHdr.Dst().SetValue(flowValues.InnHdrDstIP) - // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 - // Below code will be uncommented once ixia issue is fixed. - // if len(flowValues.inHdrDscp) != 0 { - // innerIpHdr.Priority().Dscp().Phb().SetValues(flowValues.inHdrDscp) - // } + innerIpHdr.Priority().Raw().SetValue(innerTrafficClass) UDPHeader := flow.Packet().Add().Udp() UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) @@ -853,11 +880,7 @@ func createFlow(flowValues *flowArgs) gosnappi.Flow { innerIpv6Hdr := flow.Packet().Add().Ipv6() innerIpv6Hdr.Src().SetValue(flowValues.InnHdrSrcIPv6) innerIpv6Hdr.Dst().SetValue(flowValues.InnHdrDstIPv6) - // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 - // Below code will be uncommented once ixia issue is fixed. - // if len(flowValues.inHdrDscp) != 0 { - // innerIpv6Hdr.FlowLabel().SetValues(flowValues.inHdrDscp) - // } + innerIpv6Hdr.TrafficClass().SetValue(innerTrafficClass) UDPHeader := flow.Packet().Add().Udp() UDPHeader.DstPort().Increment().SetStart(1).SetCount(50000).SetStep(1) UDPHeader.SrcPort().Increment().SetStart(1).SetCount(50000).SetStep(1) @@ -868,7 +891,7 @@ func createFlow(flowValues *flowArgs) gosnappi.Flow { func verifyBgpTelemetry(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() - t.Logf("Verifying BGP state.") + 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) @@ -1229,7 +1252,7 @@ func configGribiBaselineAFT(ctx context.Context, t *testing.T, dut *ondatra.DUTD chk.IgnoreOperationID(), ) - // Install an 0/0 static route in ENCAP_VRF_A and ENCAP_VRF_B pointing to the DEFAULT VRF. + // Install an 0/0 route in ENCAP_VRF_A and ENCAP_VRF_B pointing to the DEFAULT VRF. args.client.Modify().AddEntry(t, fluent.NextHopEntry().WithNetworkInstance(deviations.DefaultNetworkInstance(dut)). WithIndex(60).WithNextHopNetworkInstance(deviations.DefaultNetworkInstance(dut)), @@ -1579,6 +1602,8 @@ type packetValidation struct { portName string outDstIP []string inHdrIP string + inHdrDscp uint32 + inHdrEcn uint32 validateDecap bool validateTTL bool validateNoDecap bool @@ -1586,94 +1611,204 @@ type packetValidation struct { } func captureAndValidatePackets(t *testing.T, args *testArgs, packetVal *packetValidation) { + if !(packetVal.validateDecap || packetVal.validateTTL || + packetVal.validateNoDecap || packetVal.validateEncap) { + t.Fatalf(`no validation has been specified for the captured packets. + please specify at least one of the validation flags: validateDecap, validateTTL, validateNoDecap, validateEncap`) + return + } bytes := args.otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(packetVal.portName)) f, err := os.CreateTemp("", "pcap") if err != nil { - t.Fatalf("ERROR: Could not create temporary pcap file: %v\n", err) + t.Fatalf("could not create temporary pcap file: %v\n", err) } if _, err := f.Write(bytes); err != nil { - t.Fatalf("ERROR: Could not write bytes to pcap file: %v\n", err) + t.Fatalf("could not write bytes to pcap file: %v\n", err) } f.Close() - handle, err := pcap.OpenOffline(f.Name()) - if err != nil { - log.Fatal(err) - } - defer handle.Close() - packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) if packetVal.validateTTL { - validateTrafficTTL(t, packetSource) + validateTrafficTTL(t, f) } if packetVal.validateDecap { - validateTrafficDecap(t, packetSource) + validateTrafficDecap(t, f, packetVal.inHdrDscp, packetVal.inHdrEcn) } if packetVal.validateNoDecap { - validateTrafficNonDecap(t, packetSource, packetVal.outDstIP[0], packetVal.inHdrIP) + validateTrafficNonDecap(t, f, packetVal.outDstIP[0], packetVal.inHdrIP) } if packetVal.validateEncap { - validateTrafficEncap(t, packetSource, packetVal.outDstIP, packetVal.inHdrIP) + validateTrafficEncap(t, f, packetVal.outDstIP, packetVal.inHdrIP) } args.otgConfig.Captures().Clear() args.otg.PushConfig(t, args.otgConfig) time.Sleep(30 * time.Second) } -func validateTrafficTTL(t *testing.T, packetSource *gopacket.PacketSource) { +func validateTrafficTTL(t *testing.T, captureFile *os.File) { t.Helper() + pcapFileHandle, err := pcap.OpenOffline(captureFile.Name()) + if err != nil { + t.Fatal(err) + } + defer pcapFileHandle.Close() var packetCheckCount uint32 = 0 + packetSource := gopacket.NewPacketSource(pcapFileHandle, pcapFileHandle.LinkType()) for packet := range packetSource.Packets() { ipLayer := packet.Layer(layers.LayerTypeIPv4) if ipLayer != nil && packetCheckCount <= 3 { packetCheckCount++ ipPacket, _ := ipLayer.(*layers.IPv4) - if ipPacket.TTL != correspondingTTL { - t.Errorf("IP TTL value is altered to: %d", ipPacket.TTL) + if ipPacket.TTL != correspondingTTL-1 { + t.Errorf("validateTrafficTTL: after decap TTL value is not as expected: %d", ipPacket.TTL) } innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) ipv6InnerLayer := innerPacket.Layer(layers.LayerTypeIPv6) if ipInnerLayer != nil { - t.Errorf("Packets are not decapped, Inner IP header is not removed.") + t.Errorf("validateTrafficTTL: packets are not decapped, inner IP header is not removed") } if ipv6InnerLayer != nil { - t.Errorf("Packets are not decapped, Inner IPv6 header is not removed.") + t.Errorf("validateTrafficTTL: packets are not decapped, inner IPv6 header is not removed") } } } + if packetCheckCount == 0 { + t.Errorf("validateTrafficTTL: no packets have been captured and validated for TTL") + } +} + +// isDecapped determines if an IP-in-IP packet has been successfully decapsulated by verifying that only one IP layer exists. +// Layer processing is intentionally stopped at the UDP layer, as the gopacket library may misinterpret the UDP payload +// as an additional protocol layer (e.g.: APSP). This workaround prevents a false negative in the decapsulation check. +func isDecapped(t *testing.T, packet gopacket.Packet) bool { + numberOfIPLayers := 0 + for _, layer := range packet.Layers() { + if layer.LayerType() == layers.LayerTypeUDP { + break + } + if layer.LayerType() == layers.LayerTypeIPv4 || layer.LayerType() == layers.LayerTypeIPv6 { + numberOfIPLayers++ + } + } + return (numberOfIPLayers == 1) } -func validateTrafficDecap(t *testing.T, packetSource *gopacket.PacketSource) { +func validateTrafficDecap(t *testing.T, captureFile *os.File, expectedInHdrDscp uint32, expectedInHdrEcn uint32) { t.Helper() + pcapFileHandle, err := pcap.OpenOffline(captureFile.Name()) + if err != nil { + log.Fatal(err) + } + defer pcapFileHandle.Close() + testStats := struct { + packetCheckCount uint32 + IPv4CapturedPackets uint32 + IPv4NotDecappedPackets uint32 + IPv4DscpMismatchPackets uint32 + IPv4EcnMismatchPackets uint32 + IPv6CapturedPackets uint32 + IPv6NotDecappedPackets uint32 + IPv6DscpMismatchPackets uint32 + IPv6EcnMismatchPackets uint32 + }{} + packetSource := gopacket.NewPacketSource(pcapFileHandle, pcapFileHandle.LinkType()) for packet := range packetSource.Packets() { ipLayer := packet.Layer(layers.LayerTypeIPv4) - if ipLayer == nil { + ipv6Layer := packet.Layer(layers.LayerTypeIPv6) + if ipLayer == nil && ipv6Layer == nil { + // Not a packet we care about. e.g: ISIS packets. continue } - ipPacket, _ := ipLayer.(*layers.IPv4) - innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) - ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) - ipv6InnerLayer := innerPacket.Layer(layers.LayerTypeIPv6) - if ipInnerLayer != nil { - t.Errorf("Packets are not decapped, Inner IP header is not removed.") + if packet.Layer(layers.LayerTypeUDP) == nil { + continue } - if ipv6InnerLayer != nil { - t.Errorf("Packets are not decapped, Inner IPv6 header is not removed.") + testStats.packetCheckCount++ + if ipLayer != nil { + testStats.IPv4CapturedPackets++ + ipPacket, _ := ipLayer.(*layers.IPv4) + if !isDecapped(t, packet) { + innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) + ipv6InnerLayer := innerPacket.Layer(layers.LayerTypeIPv6) + if ipInnerLayer != nil { + testStats.IPv4NotDecappedPackets++ + log.Warningf("validateTrafficDecap: packets are not decapped, inner IP header is not removed") + continue + } + if ipv6InnerLayer != nil { + testStats.IPv6NotDecappedPackets++ + log.Warningf("validateTrafficDecap: packets are not decapped, inner IPv6 header is not removed") + continue + } + } + if actualDscp := uint32(ipPacket.TOS >> 2); actualDscp != expectedInHdrDscp { + testStats.IPv4DscpMismatchPackets++ + t.Errorf("validateTrafficDecap: dscp value mismatch, got %d, want %d", actualDscp, expectedInHdrDscp) + } + if actualEcn := uint32(ipPacket.TOS & 0x03); actualEcn != expectedInHdrEcn { + testStats.IPv4EcnMismatchPackets++ + t.Errorf("validateTrafficDecap: ecn value mismatch, got %d, want %d", actualEcn, expectedInHdrEcn) + } + } else { + testStats.IPv6CapturedPackets++ + ipv6Packet, _ := ipv6Layer.(*layers.IPv6) + if actualDscp := uint32(ipv6Packet.TrafficClass); actualDscp != expectedInHdrDscp { + testStats.IPv6DscpMismatchPackets++ + t.Errorf("validateTrafficDecap: dscp value mismatch, got %d, want %d", actualDscp, expectedInHdrDscp) + } + if actualEcn := uint32(ipv6Packet.TrafficClass & 0x03); actualEcn != expectedInHdrEcn { + testStats.IPv6EcnMismatchPackets++ + t.Errorf("validateTrafficDecap: ecn value mismatch, got %d, want %d", actualEcn, expectedInHdrEcn) + } } } + if testStats.packetCheckCount == 0 { + t.Errorf("validateTrafficDecap: no packets have been captured and validated for decap") + } + if testStats.IPv4DscpMismatchPackets > 0 { + t.Errorf("validateTrafficDecap:%v packets have unexpected DSCP value after decap out of %v IPv4 packets captured", + testStats.IPv4DscpMismatchPackets, testStats.IPv4CapturedPackets) + } + if testStats.IPv4EcnMismatchPackets > 0 { + t.Errorf("validateTrafficDecap:%v packets have unexpected ECN value after decap out of %v IPv4 packets captured", + testStats.IPv4EcnMismatchPackets, testStats.IPv4CapturedPackets) + } + if testStats.IPv4NotDecappedPackets > 0 { + t.Errorf("validateTrafficDecap:%v packets have not been decapped out of %v IPv4 packets captured", + testStats.IPv4NotDecappedPackets, testStats.IPv4CapturedPackets) + } + if testStats.IPv6DscpMismatchPackets > 0 { + t.Errorf("validateTrafficDecap:%v packets have unexpected DSCP value after decap out of %v IPv6 packets captured", + testStats.IPv6DscpMismatchPackets, testStats.IPv6CapturedPackets) + } + if testStats.IPv6EcnMismatchPackets > 0 { + t.Errorf("validateTrafficDecap:%v packets have unexpected ECN value after decap out of %v IPv6 packets captured", + testStats.IPv6EcnMismatchPackets, testStats.IPv6CapturedPackets) + } + if testStats.IPv6NotDecappedPackets > 0 { + t.Errorf("validateTrafficDecap:%v packets have not been decapped out of %v IPv6 packets captured", + testStats.IPv6NotDecappedPackets, testStats.IPv6CapturedPackets) + } } -func validateTrafficNonDecap(t *testing.T, packetSource *gopacket.PacketSource, outDstIP, inHdrIP string) { +func validateTrafficNonDecap(t *testing.T, captureFile *os.File, outDstIP, inHdrIP string) { t.Helper() t.Log("Validate traffic non decap routes") - var packetCheckCount uint32 = 1 + pcapFileHandle, err := pcap.OpenOffline(captureFile.Name()) + if err != nil { + log.Fatal(err) + } + defer pcapFileHandle.Close() + var packetCheckCount uint32 = 0 + packetSource := gopacket.NewPacketSource(pcapFileHandle, pcapFileHandle.LinkType()) for packet := range packetSource.Packets() { - if packetCheckCount >= 5 { - break - } ipLayer := packet.Layer(layers.LayerTypeIPv4) if ipLayer == nil { continue } + if packetCheckCount >= 5 { + break + } + packetCheckCount++ ipPacket, _ := ipLayer.(*layers.IPv4) innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) @@ -1685,24 +1820,34 @@ func validateTrafficNonDecap(t *testing.T, packetSource *gopacket.PacketSource, if ipInnerPacket.DstIP.String() != inHdrIP { t.Errorf("Negatice test for Decap failed. Traffic sent to route which does not match the decap route are decaped") } - t.Logf("Traffic for non decap routes passed.") + t.Logf("Traffic for non decap routes passed") break } } + if packetCheckCount == 0 { + t.Errorf("validateTrafficNonDecap: no packets have been captured and validated for non decap") + } } -func validateTrafficEncap(t *testing.T, packetSource *gopacket.PacketSource, outDstIP []string, innerIP string) { +func validateTrafficEncap(t *testing.T, captureFile *os.File, outDstIP []string, innerIP string) { t.Helper() t.Log("Validate traffic non decap routes") - var packetCheckCount uint32 = 1 + pcapFileHandle, err := pcap.OpenOffline(captureFile.Name()) + if err != nil { + log.Fatal(err) + } + defer pcapFileHandle.Close() + var packetCheckCount uint32 = 0 + packetSource := gopacket.NewPacketSource(pcapFileHandle, pcapFileHandle.LinkType()) for packet := range packetSource.Packets() { - if packetCheckCount >= 5 { - break - } ipLayer := packet.Layer(layers.LayerTypeIPv4) if ipLayer == nil { continue } + if packetCheckCount >= 5 { + break + } + packetCheckCount++ ipPacket, _ := ipLayer.(*layers.IPv4) innerPacket := gopacket.NewPacket(ipPacket.Payload, ipPacket.NextLayerType(), gopacket.Default) ipInnerLayer := innerPacket.Layer(layers.LayerTypeIPv4) @@ -1716,10 +1861,13 @@ func validateTrafficEncap(t *testing.T, packetSource *gopacket.PacketSource, out t.Errorf("Packets are not encapsulated as expected") } } - t.Logf("Traffic for encap routes passed.") + t.Logf("Traffic for encap routes passed") break } } + if packetCheckCount == 0 { + t.Errorf("validateTrafficEncap: no packets have been captured and validated for encap") + } } // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 @@ -1760,11 +1908,9 @@ type flowArgs struct { InnHdrSrcIP, InnHdrDstIP string InnHdrSrcIPv6, InnHdrDstIPv6 string udp, isInnHdrV4 bool - outHdrDscp []uint32 - // TODO : https://github.com/open-traffic-generator/fp-testbed-juniper/issues/42 - // Below code will be uncommented once ixia issue is fixed. - // inHdrDscp []uint32 - proto uint32 + inHdrDscp, outHdrDscp []uint32 + inHdrEcn, outHdrEcn []uint32 + proto uint32 } // testGribiDecapMatchSrcProtoNoMatchDSCP is to validate subtest test1. @@ -1790,7 +1936,7 @@ func testGribiDecapMatchSrcProtoNoMatchDSCP(ctx context.Context, t *testing.T, d for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - t.Log("Flush existing gRIBI routes before test.") + t.Log("Flush existing gRIBI routes before test") if err := gribi.FlushAll(args.client); err != nil { t.Fatal(err) } @@ -1810,17 +1956,36 @@ func testGribiDecapMatchSrcProtoNoMatchDSCP(ctx context.Context, t *testing.T, d func(t *testing.T) { flow1 := createFlow(&flowArgs{flowName: flow4in4, - outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapNoMatch}, - InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true}) + outHdrSrcIP: ipv4OuterSrc111, + outHdrDstIP: ipv4OuterDst111, + outHdrDscp: []uint32{dscpEncapNoMatch}, + outHdrEcn: []uint32{ecnCapable1}, + isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, + InnHdrDstIP: ipv4InnerDst, + inHdrDscp: []uint32{dscpEncapA1}, // Different than outer DSCP + inHdrEcn: []uint32{ecnCongestionExperienced}}) // Different than outer ECN flow2 := createFlow(&flowArgs{flowName: flow6in4, - outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, InnHdrSrcIPv6: atePort1.IPv6, - InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false, outHdrDscp: []uint32{dscpEncapNoMatch}}) + outHdrSrcIP: ipv4OuterSrc111, + outHdrDstIP: ipv4OuterDst111, + outHdrDscp: []uint32{dscpEncapNoMatch}, + outHdrEcn: []uint32{ecnCapable1}, + isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDst, + inHdrDscp: []uint32{dscpEncapA1}, // Different than outer DSCP + inHdrEcn: []uint32{ecnCongestionExperienced}}) // Different than outer ECN sendTraffic(t, args, portList, []gosnappi.Flow{flow1, flow2}) verifyTraffic(t, args, []string{flow4in4, flow6in4}, !wantLoss) captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], - outDstIP: []string{ipv4OuterDst111}, inHdrIP: ipv4InnerDst, validateTTL: true, validateDecap: true}) + outDstIP: []string{ipv4OuterDst111}, + inHdrIP: ipv4InnerDst, + inHdrDscp: dscpEncapNoMatch, // We expect the outer DSCP to be copied to the inner packet. + inHdrEcn: ecnCapable1, // We expect the outer ECN to be copied to the inner packet. + validateTTL: true, + validateDecap: true}) }) // Test with packets with a destination address that does not match @@ -1863,7 +2028,7 @@ func testGribiDecapMatchSrcProtoDSCP(ctx context.Context, t *testing.T, dut *ond for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { - t.Log("Flush existing gRIBI routes before test.") + t.Log("Flush existing gRIBI routes before test") if err := gribi.FlushAll(args.client); err != nil { t.Fatal(err) } @@ -1883,17 +2048,36 @@ func testGribiDecapMatchSrcProtoDSCP(ctx context.Context, t *testing.T, dut *ond func(t *testing.T) { flow1 := createFlow(&flowArgs{flowName: flow4in4, - outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, outHdrDscp: []uint32{dscpEncapA1}, - InnHdrSrcIP: atePort1.IPv4, InnHdrDstIP: ipv4InnerDstNoEncap, isInnHdrV4: true}) + outHdrSrcIP: ipv4OuterSrc111, + outHdrDstIP: ipv4OuterDst111, + outHdrDscp: []uint32{dscpEncapA1}, + outHdrEcn: []uint32{ecnCapable2}, + isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, + InnHdrDstIP: ipv4InnerDstNoEncap, + inHdrDscp: []uint32{dscpEncapA1}, + inHdrEcn: []uint32{ecnCapable2}}) flow2 := createFlow(&flowArgs{flowName: flow6in4, - outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: ipv4OuterDst111, InnHdrSrcIPv6: atePort1.IPv6, - InnHdrDstIPv6: ipv6InnerDstNoEncap, isInnHdrV4: false, outHdrDscp: []uint32{dscpEncapA1}}) + outHdrSrcIP: ipv4OuterSrc111, + outHdrDstIP: ipv4OuterDst111, + outHdrDscp: []uint32{dscpEncapA1}, + outHdrEcn: []uint32{ecnCapable2}, + isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDstNoEncap, + inHdrDscp: []uint32{dscpEncapA1}, + inHdrEcn: []uint32{ecnCapable2}}) sendTraffic(t, args, portList, []gosnappi.Flow{flow1, flow2}) verifyTraffic(t, args, []string{flow4in4, flow6in4}, !wantLoss) captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], - outDstIP: []string{ipv4OuterDst111}, inHdrIP: ipv4InnerDstNoEncap, validateTTL: true, validateDecap: true}) + outDstIP: []string{ipv4OuterDst111}, + inHdrIP: ipv4InnerDstNoEncap, + inHdrDscp: dscpEncapA1, + inHdrEcn: ecnCapable2, + validateTTL: true, + validateDecap: true}) }) }) } @@ -1909,7 +2093,7 @@ func testGribiDecapMixedLenPref(ctx context.Context, t *testing.T, dut *ondatra. var traffiDstIP1 string = "192.55.200.3" var traffiDstIP2 string = "192.51.128.5" - t.Log("Flush existing gRIBI routes before test.") + t.Log("Flush existing gRIBI routes before test") if err := gribi.FlushAll(args.client); err != nil { t.Fatal(err) } @@ -1926,17 +2110,36 @@ func testGribiDecapMixedLenPref(ctx context.Context, t *testing.T, dut *ondatra. portList := []string{"port8"} t.Run("Verify packets are decap & forward with Default vrf", func(t *testing.T) { flow1 := createFlow(&flowArgs{flowName: "flow1", - outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: traffiDstIP1, InnHdrSrcIPv6: atePort1.IPv6, - InnHdrDstIPv6: ipv6InnerDst, isInnHdrV4: false, outHdrDscp: []uint32{dscpEncapNoMatch}}) + outHdrSrcIP: ipv4OuterSrc111, + outHdrDstIP: traffiDstIP1, + outHdrDscp: []uint32{dscpEncapNoMatch}, + outHdrEcn: []uint32{ecnNotCapable}, + isInnHdrV4: false, + InnHdrSrcIPv6: atePort1.IPv6, + InnHdrDstIPv6: ipv6InnerDst, + inHdrDscp: []uint32{dscpEncapNoMatch}, + inHdrEcn: []uint32{ecnNotCapable}}) flow2 := createFlow(&flowArgs{flowName: "flow2", - outHdrSrcIP: ipv4OuterSrc111, outHdrDstIP: traffiDstIP2, InnHdrSrcIP: atePort1.IPv4, - InnHdrDstIP: ipv4InnerDst, isInnHdrV4: true, outHdrDscp: []uint32{dscpEncapNoMatch}}) + outHdrSrcIP: ipv4OuterSrc111, + outHdrDstIP: traffiDstIP2, + outHdrDscp: []uint32{dscpEncapNoMatch}, + outHdrEcn: []uint32{ecnNotCapable}, + isInnHdrV4: true, + InnHdrSrcIP: atePort1.IPv4, + InnHdrDstIP: ipv4InnerDst, + inHdrDscp: []uint32{dscpEncapNoMatch}, + inHdrEcn: []uint32{ecnNotCapable}}) sendTraffic(t, args, portList, []gosnappi.Flow{flow1, flow2}) verifyTraffic(t, args, []string{"flow1", "flow2"}, !wantLoss) captureAndValidatePackets(t, args, &packetValidation{portName: portList[0], - outDstIP: []string{traffiDstIP1}, inHdrIP: ipv4InnerDst, validateTTL: false, validateDecap: true}) + outDstIP: []string{traffiDstIP1}, + inHdrIP: ipv4InnerDst, + inHdrDscp: dscpEncapNoMatch, + inHdrEcn: ecnNotCapable, + validateTTL: false, + validateDecap: true}) }) // Test with packets with a destination address that does not match @@ -1960,7 +2163,7 @@ func testGribiDecapMixedLenPref(ctx context.Context, t *testing.T, dut *ondatra. func testTunnelTrafficNoDecap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { t.Helper() - t.Log("Flush existing gRIBI routes before test.") + t.Log("Flush existing gRIBI routes before test") if err := gribi.FlushAll(args.client); err != nil { t.Fatal(err) } @@ -2031,7 +2234,7 @@ func testTunnelTrafficNoDecap(ctx context.Context, t *testing.T, dut *ondatra.DU func testTunnelTrafficMatchDefaultTerm(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { t.Helper() - t.Log("Flush existing gRIBI routes before test.") + t.Log("Flush existing gRIBI routes before test") if err := gribi.FlushAll(args.client); err != nil { t.Fatal(err) } @@ -2099,7 +2302,7 @@ func testTunnelTrafficMatchDefaultTerm(ctx context.Context, t *testing.T, dut *o func testTunnelTrafficDecapEncap(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, args *testArgs) { t.Helper() - t.Log("Flush existing gRIBI routes before test.") + t.Log("Flush existing gRIBI routes before test") if err := gribi.FlushAll(args.client); err != nil { t.Fatal(err) } @@ -2195,7 +2398,7 @@ func TestGribiDecap(t *testing.T) { configureVrfSelectionPolicyW(t, dut) }) - t.Log("Install BGP route resolved by ISIS.") + t.Log("Install BGP route resolved by ISIS") t.Log("Configure ISIS on DUT") configureISIS(t, dut, []string{dut.Port(t, "port8").Name(), loopbackIntfName}, dutAreaAddress, dutSysID) @@ -2253,20 +2456,17 @@ func TestGribiDecap(t *testing.T) { }) t.Run("Test-3: Mixed Prefix Decap gRIBI Entries", func(t *testing.T) { - if deviations.GribiDecapMixedPlenUnsupported(dut) { - t.Skip("Gribi route programming with mixed prefix length is not supported.") - } testGribiDecapMixedLenPref(ctx, t, dut, args) }) - t.Log("Delete vrf selection policy W and Apply vrf selectioin policy C.") + t.Log("Delete vrf selection policy W and Apply vrf selectioin policy C") configureVrfSelectionPolicyC(t, dut) t.Run("Test-4: Tunneled traffic with no decap", func(t *testing.T) { testTunnelTrafficNoDecap(ctx, t, dut, args) }) - t.Log("Delete vrf selection policy C and Apply vrf selectioin policy W.") + t.Log("Delete vrf selection policy C and Apply vrf selectioin policy W") configureVrfSelectionPolicyW(t, dut) t.Run("Test-5: Match on default term and send to default VRF", func(t *testing.T) { diff --git a/feature/interface/aggregate/feature.textproto b/feature/interface/aggregate/feature.textproto deleted file mode 100644 index 5345b167a3f..00000000000 --- a/feature/interface/aggregate/feature.textproto +++ /dev/null @@ -1,108 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "interface_aggregate" - version: 1 -} - -# Interface settings for aggregate interface. -config_path { - path: "/interfaces/interface/aggregation/config/lag-type" -} -telemetry_path { - path: "/interfaces/interface/aggregation/state/lag-type" -} -config_path { - path: "/interfaces/interface/aggregation/config/min-links" -} -telemetry_path { - path: "/interfaces/interface/aggregation/state/min-links" -} - -# Interface settings for member interface. -config_path { - path: "/interfaces/interface/ethernet/config/aggregate-id" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/aggregate-id" -} - -# LACP global config -config_path { - path: "/lacp/config/system-priority" -} -telemetry_path { - path: "/lacp/state/system-priority" -} - -# LACP interface config -config_path { - path: "/lacp/interfaces/interface/config/name" -} -telemetry_path { - path: "/lacp/interfaces/interface/state/name" -} -config_path { - path: "/lacp/interfaces/interface/config/interval" -} -telemetry_path { - path: "/lacp/interfaces/interface/state/interval" -} -config_path { - path: "/lacp/interfaces/interface/config/lacp-mode" -} -telemetry_path { - path: "/lacp/interfaces/interface/state/lacp-mode" -} -config_path { - path: "/lacp/interfaces/interface/config/system-id-mac" -} -telemetry_path { - path: "/lacp/interfaces/interface/state/system-id-mac" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-unknown-errors" -} - -# LACP telemetry-only -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-in-pkts" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-out-pkts" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/counters/lacp-rx-errors" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/oper-key" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/partner-id" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/system-id" -} -telemetry_path { - path: "/lacp/interfaces/interface/members/member/state/port-num" -} - -feature_profile_dependency { - name: "interface_singleton" - version: 1 -} diff --git a/feature/interface/aggregate/otg_tests/balancing_test/balancing_test.go b/feature/interface/aggregate/otg_tests/balancing_test/balancing_test.go index 1d65d5372b2..34000b79860 100644 --- a/feature/interface/aggregate/otg_tests/balancing_test/balancing_test.go +++ b/feature/interface/aggregate/otg_tests/balancing_test/balancing_test.go @@ -652,6 +652,8 @@ func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice, ni string) { "0": oc.UnionString(ateDst.IPv6), }, } + dutConfNIPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)) + gnmi.Replace(t, dut, dutConfNIPath.Type().Config(), oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) if _, err := cfgplugins.NewStaticRouteCfg(b, sV6, dut); err != nil { t.Fatalf("Failed to configure IPv6 static route: %v", err) } diff --git a/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto b/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto index 84309007a1e..41bf9fb0a1a 100644 --- a/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto +++ b/feature/interface/aggregate/otg_tests/balancing_test/metadata.textproto @@ -22,6 +22,7 @@ platform_exceptions: { explicit_interface_in_default_vrf: true aggregate_atomic_update: true interface_enabled: true + static_protocol_name: "static" } } platform_exceptions: { diff --git a/feature/interface/benchmark/otg_tests/dev_thruput/README.md b/feature/interface/benchmark/otg_tests/dev_thruput/README.md new file mode 100644 index 00000000000..0a241955053 --- /dev/null +++ b/feature/interface/benchmark/otg_tests/dev_thruput/README.md @@ -0,0 +1,229 @@ +# INT-1.1: Interface Performance +## Summary + +Test performance of interfaces using a "snake style topology" and IPv4 and IPv6 packet flows. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* ATE port1 - Used for traffic Source +* ATE port2 - Used for traffic Destination + +## Topology + +VLAN1 | VLAN2 | VLAN3 | VLAN4 | VLAN5 | VLAN6 | +:----------------| :---------| :-------- | :---------| :----------| :---------| +ATE1, DUT1, DUT2 | DUT3-DUT4 | DUT5-DUT6 | DUT7-DUT8 | DUT9-DUT10 | DUT11-DUT12 + +VLAN12 | VLAN13 | VLAN14 | VLAN15 | VLAN16 | VLAN17 | +:------------| :-----------| :---------- | :---------| :------------| :--------------| +DUT23, DUT24 | DUT25-DUT26 | DUT27-DUT28 | DUT29-DUT30 | DUT31-DUT32 | DUT33-DUT34-ATE2 + +ATE-PORT1, DUT-PORT1 & DUT-PORT2 ---> VLAN1\ +DUT-PORT3 & DUT-PORT4 ---> VLAN2\ +DUT-PORT5 & DUT-PORT6 ---> VLAN3\ +DUT-PORT7 & DUT-PORT8 ---> VLAN4\ +DUT-PORT9 & DUT-PORT10 ---> VLAN5\ +DUT-PORT11 & DUT-PORT12 ---> VLAN6\ +DUT-PORT13 & DUT-PORT14 ---> VLAN7\ +DUT-PORT15 & DUT-PORT16 ---> VLAN8\ +DUT-PORT17 & DUT-PORT18 ---> VLAN9\ +DUT-PORT19 & DUT-PORT20 ---> VLAN10\ +DUT-PORT21 & DUT-PORT22 ---> VLAN11\ +DUT-PORT23 & DUT-PORT24 ---> VLAN12\ +DUT-PORT25 & DUT-PORT26 ---> VLAN13\ +DUT-PORT27 & DUT-PORT28 ---> VLAN14\ +DUT-PORT29 & DUT-PORT30 ---> VLAN15\ +DUT-PORT31 & DUT-PORT32 ---> VLAN16\ +DUT-PORT33 & DUT-PORT34 & ATE-PORT2 ---> VLAN17 + +## Procedure + +### Testbed setup - Generate configuration for ATE and DUT + +#### DUT Configuration + * Create 18 VLAN's on the DUT from VLAN1 to VLAN18 and are all configured as Access VLAN + * Assign IPv4, IPv6 addresses to ATE-PORT1, ATE-PORT2 + ATE-PORT1 - IPv4 address 192.168.1.1/24; IPv6 address 2000:1:1:1::1/64 + ATE-PORT1 - IPv4 address 193.168.1.1/24; IPv6 address 2001:1:1:1::1/64 + * Traffic enters from ATE-PORT1 to DUT:PORT1 tagged/untagged and it is forwarded to + DUT:PORT2 part of VLAN1. Then traffic is forwarded to DUT:PORT3 because it is + back-back connected. Once it enters DUT:PORT3, traffic is forwarded to DUT:PORT4 + and similar to the earlier scenario traffic enters into the DUT:PORT5 and follows + same till we reach the DUT-PORT36 of VLAN18 which is connected to ATE-PORT2 + * Traffic entering from ATE-PORT2 follows similar forwarding mechanism as shown + in the previous step + +#### Traffic profile + * Create 6 traffic profiles as below + +##### Traffic-ipv4-framesize-64bytes + * Create ipv4 traffic profile which has following properties + - Frame size 64 bytes + - Line rate traffic (100%) / 595millon packets per second (pps) + +##### Traffic-ipv4-framesize-mixed + * Create ipv4 traffic profile which has following properties + - Frame size mixed (64-1518) with an average packet size of 760bytes + - Line rate traffic (100%) / ~74million packets per second (pps) + +##### Traffic-ipv4-framesize-jumbo-9000bytes + * Create ipv4 traffic profile which has following properties + - Frame size 9000 bytes + - Line rate traffic (100%) / 5.5million packets per second (pps) + +##### Traffic-ipv6-framesize-64bytes + * Create ipv6 traffic profile which has following properties + - Frame size 64 bytes + - Line rate traffic (100%) / 595millon packets per second (pps) + +##### Traffic-ipv6-framesize-mixed + * Create ipv6 traffic profile which has following properties + - Frame size mixed (64-1518) with an average frame size of 760 + - Line rate traffic (100%) / ~74million packets per second (pps) + +##### Traffic-ipv6-framesize-jumbo-9000bytes + * Create ipv6 traffic profile which has following properties + - Frame size 9000 bytes + - Line rate traffic (100%) / 5.5million packets per second (pps) + + +## Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "aggregation": { + "config": { + "lag-type": "LACP", + "min-links": 1 + } + }, + "config": { + "name": "ae0" + }, + "name": "ae0" + }, + { + "config": { + "loopback-mode": "FACILITY", + "name": "eth0" + }, + "ethernet": { + "config": { + "aggregate-id": "ae0", + "duplex-mode": "FULL", + "port-speed": "SPEED_10GB" + } + }, + "name": "eth0" + } + ] + } +} +``` + +## TestCase-1: +### INT-1.1.1 Test IPv4 traffic 400G throughput +#### Start test + * Start traffic profile "Traffic-ipv4-framesize-64bytes" described above + +#### Verification + * Verify that each port on the device shows 400G in and out traffic statistics + * Verify that the traffic sent from ATE:PORT1 as source is recieved on ATE:PORT2 + as destination + * Make sure there is 0 drop in packets + * Verify CPU utilization, Power utilization and it should be normal + +### INT-1.1.2 Test IPv4 traffic 400G throughput +#### Start test + * Start traffic profile "Traffic-ipv4-framesize-mixed" described above + +#### Verification + * Verify that each port on the device shows 400G in and out traffic statistics + * Verify that the traffic sent from ATE:PORT1 as source is recieved on ATE:PORT2 + as destination + * Make sure there is 0 drop in packets + * Verify CPU utilization, Power utilization and it should be normal + + +### INT-1.1.3 Test IPv4 traffic 400G throughput +#### Start test + * Start traffic profile "Traffic-ipv4-framesize-jumbo-9000bytes" described above + +#### Verification + * Verify that each port on the device shows 400G in and out traffic statistics + * Verify that the traffic sent from ATE:PORT1 as source is recieved on ATE:PORT2 + as destination + * Make sure there is 0 drop in packets + * Verify CPU utilization, Power utilization and it should be normal + +### INT-1.2.1 Test IPv6 traffic 400G throughput +#### Start test + * Start traffic profile "Traffic-ipv6-framesize-64bytes" described above + +#### Verification + * Verify that each port on the device shows 400G in and out traffic statistics + * Verify that the traffic sent from ATE:PORT1 as source is recieved on ATE:PORT2 + as destination + * Make sure there is 0 drop in packets + * Verify CPU utilization, Power utilization and it should be normal + +### INT-1.2.2 Test IPv6 traffic 400G throughput +#### Start test + * Start traffic profile "Traffic-ipv6-framesize-mixed" described above + +#### Verification + * Verify that each port on the device shows 400G in and out traffic statistics + * Verify that the traffic sent from ATE:PORT1 as source is recieved on ATE:PORT2 + as destination + * Make sure there is 0 drop in packets + * Verify CPU utilization, Power utilization and it should be normal + +### INT-1.2.3 Test IPv6 traffic 400G throughput +#### Start test + * Start traffic profile "Traffic-ipv6-framesize-jumbo-9000bytes" described above + +#### Verification + * Verify that each port on the device shows 400G in and out traffic statistics + * Verify that the traffic sent from ATE:PORT1 as source is recieved on ATE:PORT2 + as destination + * Make sure there is 0 drop in packets + * Verify CPU utilization, Power utilization and it should be normal + +## 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 +openconfig_paths: + ## Config paths + /interfaces/interface/config/name: + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + + ## Telemetry paths + /interfaces/interface/state/loopback-mode: + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/in-octets: + /interfaces/interface/state/counters/in-pkts: + /interfaces/interface/state/counters/in-unicast-pkts: + /interfaces/interface/state/counters/out-discards: + /interfaces/interface/state/counters/out-errors: + /interfaces/interface/state/counters/out-octets: + /interfaces/interface/state/counters/out-pkts: + /interfaces/interface/state/counters/out-unicast-pkts: +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement +* FFF +* 32 PORTS CONNECTED B2B wired + + diff --git a/feature/interface/benchmark/otg_tests/dev_thruput/metadata.textproto b/feature/interface/benchmark/otg_tests/dev_thruput/metadata.textproto new file mode 100644 index 00000000000..1c8a5043877 --- /dev/null +++ b/feature/interface/benchmark/otg_tests/dev_thruput/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ee3fdf0d-610f-4808-899a-8d0057fa6962" +plan_id: "INT-1.1" +description: "Interface Performance" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/interface/holdtime/feature.textproto b/feature/interface/holdtime/feature.textproto deleted file mode 100644 index 26920b39624..00000000000 --- a/feature/interface/holdtime/feature.textproto +++ /dev/null @@ -1,78 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "interface_holdtime" - version: 1 -} - -# Interface basic config. -config_path { - path: "/interfaces/interface/config/name" -} -telemetry_path { - path: "/interfaces/interface/state/name" -} -config_path { - path: "/interfaces/interface/config/id" -} -telemetry_path { - path: "/interfaces/interface/state/id" -} -config_path { - path: "/interfaces/interface/config/type" -} -telemetry_path { - path: "/interfaces/interface/state/type" -} -config_path { - path: "/interfaces/interface/config/enabled" -} -telemetry_path { - path: "/interfaces/interface/state/enabled" -} -telemetry_path { - path: "/interfaces/interface/state/oper-status" -} -telemetry_path { - path: "/interfaces/interface/state/admin-status" -} - -# Interface hold timers -config_path { - path: "/interfaces/interface/hold-time/config/down" -} -telemetry_path { - path: "/interfaces/interface/hold-time/state/down" -} -config_path { - path: "/interfaces/interface/hold-time/config/up" -} -telemetry_path { - path: "/interfaces/interface/hold-time/state/up" -} - -# Ethernet config -config_path { - path: "/interfaces/interface/ethernet/config/port-speed" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/port-speed" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/negotiated-port-speed" -} diff --git a/feature/interface/holdtime/otg_tests/holdtime_test/holddown_timers_test.go b/feature/interface/holdtime/otg_tests/holdtime_test/holddown_timers_test.go index d4b5326acc6..198d11c4ada 100644 --- a/feature/interface/holdtime/otg_tests/holdtime_test/holddown_timers_test.go +++ b/feature/interface/holdtime/otg_tests/holdtime_test/holddown_timers_test.go @@ -69,16 +69,21 @@ func setupAggregateAtomically(t *testing.T, dut *ondatra.DUTDevice, aggPorts []* t.Helper() d := &oc.Root{} agg := d.GetOrCreateInterface(aggID) - agg.Type = oc.IETFInterfaces_InterfaceType_ieee8023adLag - agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_STATIC + agg.SetType(oc.IETFInterfaces_InterfaceType_ieee8023adLag) + agg.GetOrCreateAggregation().SetLagType(oc.IfAggregate_AggregationType_STATIC) for _, port := range aggPorts { i := d.GetOrCreateInterface(port.Name()) + if deviations.FrBreakoutFix(dut) && port.PMD() == ondatra.PMD100GBASEFR { + i.GetOrCreateEthernet().SetAutoNegotiate(false) + i.GetOrCreateEthernet().SetDuplexMode(oc.Ethernet_DuplexMode_FULL) + i.GetOrCreateEthernet().SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB) + } i.GetOrCreateEthernet().AggregateId = ygot.String(aggID) - i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.SetType(oc.IETFInterfaces_InterfaceType_ethernetCsmacd) if deviations.InterfaceEnabled(dut) { - i.Enabled = ygot.Bool(true) + i.SetEnabled(true) } } gnmi.Update(t, dut, gnmi.OC().Config(), d) @@ -87,11 +92,15 @@ func setupAggregateAtomically(t *testing.T, dut *ondatra.DUTDevice, aggPorts []* func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, aggPorts []*ondatra.Port, aggID string) { t.Helper() + b := &gnmi.SetBatch{} if deviations.AggregateAtomicUpdate(dut) { // Clear aggregate & ip config on ports. for _, port := range aggPorts { - gnmi.Delete(t, dut, gnmi.OC().Interface(port.Name()).Ethernet().Config()) + gnmi.BatchDelete(b, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + gnmi.BatchDelete(b, gnmi.OC().Interface(port.Name()).Subinterface(0).Ipv4().Config()) + gnmi.BatchDelete(b, gnmi.OC().Interface(port.Name()).Subinterface(0).Ipv6().Config()) } + b.Set(t, dut) setupAggregateAtomically(t, dut, aggPorts, aggID) } agg := dutDst.NewOCInterface(aggID, dut) @@ -103,11 +112,18 @@ func configureDUTBundle(t *testing.T, dut *ondatra.DUTDevice, aggPorts []*ondatr d := &oc.Root{} i := d.GetOrCreateInterface(port.Name()) + if deviations.FrBreakoutFix(dut) { + if port.PMD() == ondatra.PMD100GBASEFR && dut.Vendor() == ondatra.ARISTA { + i.GetOrCreateEthernet().SetAutoNegotiate(false) + i.GetOrCreateEthernet().SetDuplexMode(oc.Ethernet_DuplexMode_FULL) + i.GetOrCreateEthernet().SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB) + } + } i.GetOrCreateEthernet().AggregateId = ygot.String(aggID) - i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.SetType(oc.IETFInterfaces_InterfaceType_ethernetCsmacd) if deviations.InterfaceEnabled(dut) { - i.Enabled = ygot.Bool(true) + i.SetEnabled(true) } gnmi.Replace(t, dut, gnmi.OC().Interface(port.Name()).Config(), i) } diff --git a/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto b/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto index c5a925a1210..235d5965bbd 100644 --- a/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto +++ b/feature/interface/holdtime/otg_tests/holdtime_test/metadata.textproto @@ -22,5 +22,6 @@ platform_exceptions: { interface_enabled: true default_network_instance: "default" missing_value_for_defaults: true + fr_breakout_fix: true } } diff --git a/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md index aea9ddcf069..9eb21d7095c 100644 --- a/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md +++ b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/README.md @@ -11,7 +11,7 @@ Ensure Interface mode can be set to loopback mode and can be added as part of st * Configure DUT port-1 to OTG port-1. * Admin down OTG port-1. * Verify DUT port-1 is down. -* On DUT port-1, set interface “loopback mode” to “FACILITY”. +* On DUT port-1, set interface “loopback mode” to “TERMINAL”. * Add port-1 as part of Static LAG (lacp mode static(on)). * Validate that port-1 operational status is “UP”. * Validate on DUT that LAG interface status is “UP”. @@ -37,3 +37,40 @@ rpcs: gnmi: gNMI.Set: gNMI.Subscribe: +``` + +#### Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "aggregation": { + "config": { + "lag-type": "LACP", + "min-links": 1 + } + }, + "config": { + "name": "ae0" + }, + "name": "ae0" + }, + { + "config": { + "loopback-mode": "FACILITY", + "name": "eth0" + }, + "ethernet": { + "config": { + "aggregate-id": "ae0", + "duplex-mode": "FULL", + "port-speed": "SPEED_10GB" + } + }, + "name": "eth0" + } + ] + } +} +``` diff --git a/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go index dcbc768fe6f..3f80f501f51 100644 --- a/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go +++ b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/interface_loopback_aggregate_test.go @@ -19,6 +19,8 @@ import ( "testing" "time" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/open-traffic-generator/snappi/gosnappi" "github.com/openconfig/featureprofiles/internal/attrs" "github.com/openconfig/featureprofiles/internal/deviations" @@ -120,6 +122,7 @@ func configureOTG(t *testing.T, otg *otg.OTG) { iDut1Eth.Connection().SetPortName(port1.Name()) t.Logf("Pushing config to ATE and starting protocols...") otg.PushConfig(t, config) + time.Sleep(20 * time.Second) otg.StartProtocols(t) } @@ -154,7 +157,7 @@ func configAggregateIntf(dut *ondatra.DUTDevice, i *oc.Interface, a *attrs.Attri g.LagType = lagType } -// TestInterfaceLoopbackMode is to test loopback mode FACILITY. +// TestInterfaceLoopbackMode is to test loopback mode TERMINAL. func TestInterfaceLoopbackMode(t *testing.T) { t.Logf("Start DUT config load.") dut := ondatra.DUT(t, "dut") @@ -187,7 +190,7 @@ func TestInterfaceLoopbackMode(t *testing.T) { }) t.Run("Verify DUT port-1 is down on DUT", func(t *testing.T) { - gnmi.Await(t, dut, gnmi.OC().Interface(dutPort1.Name()).OperStatus().State(), 1*time.Minute, oc.Interface_OperStatus_DOWN) + gnmi.Await(t, dut, gnmi.OC().Interface(dutPort1.Name()).OperStatus().State(), 2*time.Minute, oc.Interface_OperStatus_DOWN) operStatus := gnmi.Get(t, dut, gnmi.OC().Interface(dutPort1.Name()).OperStatus().State()) if want := oc.Interface_OperStatus_DOWN; operStatus != want { t.Errorf("Get(DUT port1 oper status): got %v, want %v", operStatus, want) @@ -228,17 +231,11 @@ func TestInterfaceLoopbackMode(t *testing.T) { } }) - t.Run("Configure interface loopback mode FACILITY on DUT AE interface", func(t *testing.T) { - if deviations.InterfaceLoopbackModeRawGnmi(dut) { - - gnmi.Update(t, dut, gnmi.OC().Interface(dutPort1.Name()).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_TERMINAL) - + t.Run("Configure interface loopback mode TERMINAL on DUT AE interface", func(t *testing.T) { + if deviations.MemberLinkLoopbackUnsupported(dut) { + gnmi.Update(t, dut, gnmi.OC().Interface(aggID).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_TERMINAL) } else { - if deviations.MemberLinkLoopbackUnsupported(dut) { - gnmi.Update(t, dut, gnmi.OC().Interface(aggID).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_FACILITY) - } else { - gnmi.Update(t, dut, gnmi.OC().Interface(dutPort1.Name()).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_FACILITY) - } + gnmi.Update(t, dut, gnmi.OC().Interface(dutPort1.Name()).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_TERMINAL) } }) @@ -258,5 +255,11 @@ func TestInterfaceLoopbackMode(t *testing.T) { t.Run("Admin up OTG port1", func(t *testing.T) { cs.Port().Link().SetState(gosnappi.StatePortLinkState.UP) otg.SetControlState(t, cs) + if deviations.MemberLinkLoopbackUnsupported(dut) { + gnmi.Update(t, dut, gnmi.OC().Interface(aggID).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_NONE) + } else { + gnmi.Update(t, dut, gnmi.OC().Interface(dutPort1.Name()).LoopbackMode().Config(), oc.Interfaces_LoopbackModeType_NONE) + } + gnmi.Await(t, otg, gnmi.OTG().Port(ate.Port(t, "port1").ID()).Link().State(), 2*time.Minute, otgtelemetry.Port_Link_UP) }) } diff --git a/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto index ea001efc885..573c7fa9a47 100644 --- a/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto +++ b/feature/interface/interface_loopback_aggregate/otg_tests/interface_loopback_aggregate/metadata.textproto @@ -18,7 +18,6 @@ platform_exceptions: { vendor: CISCO } deviations: { - interface_loopback_mode_raw_gnmi: true } } platform_exceptions: { diff --git a/feature/interface/ip/feature.textproto b/feature/interface/ip/feature.textproto deleted file mode 100644 index b03ede8d819..00000000000 --- a/feature/interface/ip/feature.textproto +++ /dev/null @@ -1,60 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "interface_ip" - version: 1 -} - -# Interface basic config. -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/enable" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/mode" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/state/enable" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/state/mode" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/interval" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/state/interval" -} - -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip"; -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length"; -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/type"; -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/ip"; -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/prefix-length"; -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/type"; -} diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md b/feature/interface/ip/otg_tests/disable_ipv6_nd_ra_test/README.md similarity index 100% rename from feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/README.md rename to feature/interface/ip/otg_tests/disable_ipv6_nd_ra_test/README.md diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go b/feature/interface/ip/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go similarity index 100% rename from feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go rename to feature/interface/ip/otg_tests/disable_ipv6_nd_ra_test/disable_ipv6_nd_ra_test.go diff --git a/feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto b/feature/interface/ip/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto similarity index 100% rename from feature/interface/ip/ipv6_ND/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto rename to feature/interface/ip/otg_tests/disable_ipv6_nd_ra_test/metadata.textproto diff --git a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md b/feature/interface/ip/otg_tests/ipv6_link_local_test/README.md similarity index 100% rename from feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/README.md rename to feature/interface/ip/otg_tests/ipv6_link_local_test/README.md diff --git a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go b/feature/interface/ip/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go similarity index 100% rename from feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go rename to feature/interface/ip/otg_tests/ipv6_link_local_test/ipv6_link_local_test.go diff --git a/feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto b/feature/interface/ip/otg_tests/ipv6_link_local_test/metadata.textproto similarity index 100% rename from feature/interface/ip/ipv6_link_local/otg_tests/ipv6_link_local_test/metadata.textproto rename to feature/interface/ip/otg_tests/ipv6_link_local_test/metadata.textproto diff --git a/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/README.md b/feature/interface/ip/otg_tests/ipv6_slaac_link_local_test/README.md similarity index 100% rename from feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/README.md rename to feature/interface/ip/otg_tests/ipv6_slaac_link_local_test/README.md diff --git a/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go b/feature/interface/ip/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go similarity index 100% rename from feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go rename to feature/interface/ip/otg_tests/ipv6_slaac_link_local_test/ipv6_slaac_link_local_test.go diff --git a/feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto b/feature/interface/ip/otg_tests/ipv6_slaac_link_local_test/metadata.textproto similarity index 100% rename from feature/interface/ip/ipv6_slaac_link_local/otg_tests/ipv6_slaac_link_local_test/metadata.textproto rename to feature/interface/ip/otg_tests/ipv6_slaac_link_local_test/metadata.textproto diff --git a/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/README.md b/feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/README.md similarity index 100% rename from feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/README.md rename to feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/README.md diff --git a/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/metadata.textproto b/feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/metadata.textproto similarity index 100% rename from feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/metadata.textproto rename to feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/metadata.textproto diff --git a/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/suppress_ipv6_nd_ra_test.go b/feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/suppress_ipv6_nd_ra_test.go similarity index 94% rename from feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/suppress_ipv6_nd_ra_test.go rename to feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/suppress_ipv6_nd_ra_test.go index 3dfa2b27edf..e741818a833 100644 --- a/feature/interface/ip/ipv6_ND/otg_tests/suppress_ipv6_nd_ra_test/suppress_ipv6_nd_ra_test.go +++ b/feature/interface/ip/otg_tests/suppress_ipv6_nd_ra_test/suppress_ipv6_nd_ra_test.go @@ -116,6 +116,13 @@ func verifyOTGPacketCaptureForRA(t *testing.T, ate *ondatra.ATEDevice, config go otg := ate.OTG() otg.StartProtocols(t) + // Explicitly stop any pre-existing captures. + t.Log("Stopping any existing captures to avoid any limitation in the number of active captures") + stopCaptureState := gosnappi.NewControlState() + stopCaptureState.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, stopCaptureState) + + // Start capture. cs := gosnappi.NewControlState() cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) otg.SetControlState(t, cs) @@ -124,7 +131,7 @@ func verifyOTGPacketCaptureForRA(t *testing.T, ate *ondatra.ATEDevice, config go } time.Sleep(time.Duration(waitTime) * time.Second) - bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(config.Ports().Items()[1].Name())) + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(config.Ports().Items()[0].Name())) t.Logf("Config Ports %v", config.Ports().Items()) f, err := os.CreateTemp("", "pcap") if err != nil { diff --git a/feature/interface/singleton/feature.textproto b/feature/interface/singleton/feature.textproto deleted file mode 100644 index 62fad1c9fc6..00000000000 --- a/feature/interface/singleton/feature.textproto +++ /dev/null @@ -1,281 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "interface_singleton" - version: 1 -} - -# Interface basic config. -config_path { - path: "/interfaces/interface/config/name" -} -telemetry_path { - path: "/interfaces/interface/state/name" -} -config_path { - path: "/interfaces/interface/config/id" -} -telemetry_path { - path: "/interfaces/interface/state/id" -} -config_path { - path: "/interfaces/interface/config/description" -} -telemetry_path { - path: "/interfaces/interface/state/description" -} -config_path { - path: "/interfaces/interface/config/type" -} -telemetry_path { - path: "/interfaces/interface/state/type" -} -config_path { - path: "/interfaces/interface/config/enabled" -} -telemetry_path { - path: "/interfaces/interface/state/enabled" -} -config_path { - path: "/interfaces/interface/config/forwarding-viable" -} -telemetry_path { - path: "/interfaces/interface/state/forwarding-viable" -} -config_path { - path: "/interfaces/interface/config/loopback-mode" -} -telemetry_path { - path: "/interfaces/interface/state/loopback-mode" -} -telemetry_path { - path: "/interfaces/interface/state/oper-status" -} -telemetry_path { - path: "/interfaces/interface/state/admin-status" -} -config_path { - path: "/interfaces/interface/config/mtu" -} -telemetry_path { - path: "/interfaces/interface/state/mtu" -} -config_path { - path: "/interfaces/interface/name" -} - - -# Interface hold timers -config_path { - path: "/interfaces/interface/hold-time/config/down" -} -telemetry_path { - path: "/interfaces/interface/hold-time/state/down" -} -config_path { - path: "/interfaces/interface/hold-time/config/up" -} -telemetry_path { - path: "/interfaces/interface/hold-time/state/up" -} - -# Interface counters -telemetry_path { - path: "/interfaces/interface/state/counters/out-octets" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-broadcast-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-discards" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-errors" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-multicast-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-octets" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-unicast-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/counters/in-unknown-protos" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-broadcast-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-discards" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-errors" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-multicast-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-octets" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-pkts" -} -telemetry_path { - path: "/interfaces/interface/state/counters/out-unicast-pkts" -} - -# Ethernet config -config_path { - path: "/interfaces/interface/ethernet/config/mac-address" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/mac-address" -} -config_path { - path: "/interfaces/interface/ethernet/config/port-speed" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/port-speed" -} -config_path { - path: "/interfaces/interface/ethernet/config/duplex-mode" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/duplex-mode" -} -config_path { - path: "/interfaces/interface/ethernet/config/enable-flow-control" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/enable-flow-control" -} -config_path { - path: "/interfaces/interface/ethernet/config/auto-negotiate" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/auto-negotiate" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/counters/in-mac-pause-frames" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/counters/out-mac-pause-frames" -} -telemetry_path { - path: "/interfaces/interface/ethernet/state/negotiated-port-speed" -} - -# Sub-interface -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/config/description" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/state/description" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/config/enabled" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/state/enabled" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/config/index" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/state/index" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/index" -} - -# Sub-interface ipv4 -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/state/enabled" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/config/mtu" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/state/mtu" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/ip" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/ip" -} - -# Sub-interface ipv6 -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/enabled" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/config/mtu" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/mtu" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/ip" -} - -# Sub-interface counters -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/state/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/in-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/out-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-discarded-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/out-discarded-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-discarded-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/in-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-forwarded-pkts" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/out-pkts" -} diff --git a/feature/interface/singleton/otg_tests/singleton_test/README.md b/feature/interface/singleton/otg_tests/singleton_test/README.md index e2280169843..350dfe342ed 100644 --- a/feature/interface/singleton/otg_tests/singleton_test/README.md +++ b/feature/interface/singleton/otg_tests/singleton_test/README.md @@ -140,12 +140,6 @@ Verify that interface packet counters are properly incremented in every streamin * /interfaces/interface/ethernet/state/ * /components/component/port/breakout-mode/groups/group/state -### RT-5.1.7 -#### Enable unnumbered subinterface and check the state -* Enable a IPv4 unnumbered subinterface with a loopback interface as the interface reference -* Check that the state of the unnumbered subinterface is enabled - * /interfaces/interface/subinterfaces/subinterface/ipv4/unnumbered/state/enabled - ## Config Parameter Coverage * /interfaces/interface/config/name @@ -153,7 +147,6 @@ Verify that interface packet counters are properly incremented in every streamin * /interfaces/interface/config/enabled * /interfaces/interface/subinterfaces/subinterface/ipv4/config/mtu * /interfaces/interface/subinterfaces/subinterface/ipv6/config/mtu -* /interfaces/interface/subinterfaces/subinterface/ipv4/unnumbered/config/enabled * /interfaces/interface/config/id * /interfaces/interface/ethernet/config/mac-address * /interfaces/interface/ethernet/config/port-speed @@ -180,7 +173,6 @@ Verify that interface packet counters are properly incremented in every streamin * /interfaces/interface/state/counters/out-unicast-pkts * /interfaces/interface/subinterfaces/subinterface/ipv4/state/mtu * /interfaces/interface/subinterfaces/subinterface/ipv6/state/mtu -* /interfaces/interface/subinterfaces/subinterface/ipv4/unnumbered/state/enabled * /interfaces/interface/state/oper-status * /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/ip * /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/in-pkts @@ -237,7 +229,6 @@ paths: /interfaces/interface/state/counters/out-unicast-pkts: /interfaces/interface/subinterfaces/subinterface/ipv4/state/mtu: /interfaces/interface/subinterfaces/subinterface/ipv6/state/mtu: - /interfaces/interface/subinterfaces/subinterface/ipv4/unnumbered/state/enabled: /interfaces/interface/state/oper-status: /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/ip: /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/in-pkts: diff --git a/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go b/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go index ba4e4baea1f..c7f442bd77e 100644 --- a/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go +++ b/feature/interface/singleton/otg_tests/singleton_test/singleton_test.go @@ -30,7 +30,6 @@ import ( "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/ondatra/netutil" "github.com/openconfig/ygnmi/ygnmi" "github.com/openconfig/ygot/ygot" @@ -98,16 +97,6 @@ var ( IPv4Len: plen4, IPv6Len: plen6, } - - dutLoopback = attrs.Attributes{ - Desc: "Loopback ip", - IPv4: "192.0.2.21", - IPv6: "2001:db8::21", - IPv4Len: 32, - IPv6Len: 128, - } - - lb string ) type testCase struct { @@ -577,67 +566,3 @@ func TestMTUs(t *testing.T) { t.Run(fmt.Sprintf("MTU=%d", mtu), tc.testMTU) } } - -func (tc *testCase) configureDUTLoopback(t *testing.T, dut *ondatra.DUTDevice) { - t.Helper() - lb = netutil.LoopbackInterface(t, dut, 0) - lo0 := gnmi.OC().Interface(lb).Subinterface(0) - ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) - foundV4 := false - for _, ip := range ipv4Addrs { - if v, ok := ip.Val(); ok { - foundV4 = true - dutLoopback.IPv4 = v.GetIp() - break - } - } - if !foundV4 { - lo1 := dutLoopback.NewOCInterface(lb, dut) - lo1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback - gnmi.Update(t, dut, gnmi.OC().Interface(lb).Config(), lo1) - } -} - -func (tc *testCase) configureInterface(t *testing.T, i *oc.Interface, a *attrs.Attributes) { - a.ConfigOCInterface(i, tc.dut) - i.Description = ygot.String(*i.Description) - _ = i.GetOrCreateSubinterface(0) -} - -func (tc *testCase) configInterfaceDUTUnnumbered(i *oc.Interface, a *attrs.Attributes) { - s := i.GetOrCreateSubinterface(0) - s4 := s.GetOrCreateIpv4() - unnumebered := s4.GetOrCreateUnnumbered() - unnumebered.SetEnabled(true) - refInterface := unnumebered.GetOrCreateInterfaceRef() - refInterface.SetInterface(lb) -} - -func (tc *testCase) testUnnumberedSubInterfaceEnabled(t *testing.T) { - d := gnmi.OC() - - p1 := tc.dut.Port(t, "port1") - tc.duti1 = &oc.Interface{Name: ygot.String(p1.Name())} - tc.configureInterface(t, tc.duti1, &dutSrc) - tc.configureDUTLoopback(t, tc.dut) - tc.configInterfaceDUTUnnumbered(tc.duti1, &dutSrc) - di1 := d.Interface(p1.Name()) - fptest.LogQuery(t, p1.String(), di1.Config(), tc.duti1) - gnmi.Replace(t, tc.dut, di1.Config(), tc.duti1) - - dip := gnmi.OC().Interface(p1.Name()) - di := gnmi.Get(t, tc.dut, dip.State()) - fptest.LogQuery(t, p1.String(), dip.State(), di) - if got := di.GetSubinterface(0).GetIpv4().GetUnnumbered().GetEnabled(); got != true { - t.Errorf("Unnumbered subinterface enabled got %v, want true", got) - } -} - -// TestUnnumberedSubInterfaceEnabled tests that an subinterface can be configured with an unnumbered address. -func TestUnnumberedSubInterfaceEnabled(t *testing.T) { - dut := ondatra.DUT(t, "dut") - tc := &testCase{ - dut: dut, - } - t.Run("unnumbered_subinterface_enable", tc.testUnnumberedSubInterfaceEnabled) -} diff --git a/feature/interface/staticarp/feature.textproto b/feature/interface/staticarp/feature.textproto deleted file mode 100644 index 0dfd3be72c5..00000000000 --- a/feature/interface/staticarp/feature.textproto +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "interface_staticarp" - version: 1 -} - -# IPv4 -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/state/ip" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/prefix-length" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/state/prefix-length" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/ip" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/config/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/state/ip" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/config/link-layer-address" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/state/link-layer-address" -} - -# IPv6 -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/ip" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/state/prefix-length" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/config/ip" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/state/ip" -} -config_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/config/link-layer-address" -} -telemetry_path { - path: "/interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/state/link-layer-address" -} - -feature_profile_dependency { - name: "interface_singleton" - version: 1 -} diff --git a/feature/interface/staticarp/otg_tests/static_arp_test/README.md b/feature/interface/staticarp/otg_tests/static_arp_test/README.md index 526c4fc8e83..2a31090d2b2 100644 --- a/feature/interface/staticarp/otg_tests/static_arp_test/README.md +++ b/feature/interface/staticarp/otg_tests/static_arp_test/README.md @@ -27,23 +27,26 @@ Note that OTG ports are promiscuous, i.e. they will receive all packets regardless of the destination MAC. The custom egress filter is used to tell what are the destination MAC addresses of the packets seen by the OTG. -## Config Parameter Coverage - -* /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/ip -* /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/prefix-length -* /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/config/ip -* /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/config/link-layer-address -* /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip -* /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length -* /interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/config/ip -* /interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/config/link-layer-address - ## OpenConfig Path and RPC Coverage - ```yaml +paths: + ## Config Parameter Coverage + /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/prefix-length: + /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/config/link-layer-address: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length: + /interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv6/neighbors/neighbor/config/link-layer-address: + ## telemetry Parameter Coverage + /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/state/ip: + /interfaces/interface/subinterfaces/subinterface/ipv4/neighbors/neighbor/state/link-layer-address: + rpcs: gnmi: - gNMI.Get: + gNMI.Set: + union_replace: true gNMI.Subscribe: - -``` \ No newline at end of file + on_change: true +``` diff --git a/feature/isis/auth/feature.textproto b/feature/isis/auth/feature.textproto deleted file mode 100644 index 0300d1bc3ad..00000000000 --- a/feature/isis/auth/feature.textproto +++ /dev/null @@ -1,130 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "isis_auth" - version: 1 -} - -# Global - -# Global Authentication -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/state/authentication-check" -} - -# Levels - -# Level authentication enable -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/enabled" -} - -# Level csnp authentication -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/disable-csnp" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/disable-csnp" -} - -# Level psnp authentication -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/disable-psnp" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/disable-psnp" -} - -# Level lsp authentication -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/disable-lsp" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/disable-lsp" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/auth-fails" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/auth-type-fails" -} - -# Level authentication keychain -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/keychain" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/keychain" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-mode" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/auth-mode" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-password" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/auth-password" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/config/auth-type" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/authentication/state/auth-type" -} - -# Interfaces authentication hello authentication -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/authentication/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/authentication/state/enabled" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-type" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-mode" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/state/auth-mode" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/auth-password" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/state/auth-password" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/hello-authentication/state/enabled" -} - -# Interfaces authentication key authentication password -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/authentication/config/keychain" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/authentication/state/keychain" -} - -feature_profile_dependency { - name: "isis" - version: 1 -} - -feature_profile_dependency { - name: "networkinstance" - version: 1 -} diff --git a/feature/isis/feature.textproto b/feature/isis/feature.textproto deleted file mode 100644 index aaa62b80e57..00000000000 --- a/feature/isis/feature.textproto +++ /dev/null @@ -1,517 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "isis" - version: 3 -} - -# Global - -# Global NET -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/config/net" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/state/net" -} - -# Global Level Capability -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/config/level-capability" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/state/level-capability" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/config/hello-padding" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/state/hello-padding" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/config/instance" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/state/instance" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/config/csnp-enable-on-p2p-links" -} - -# Global lsp-bit -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/config/set-bit" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/lsp-bit/overload-bit/state/set-bit" -} - -# AFI-SAFI -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/afi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/safi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/afi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/state/afi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/state/safi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/state/enabled" -} - -# Transport -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/transport/config/lsp-mtu-size" -} - -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/transport/state/lsp-mtu-size" -} - -# Levels - -# Level number -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/level-number" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/config/level-number" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/state/level-number" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/state/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/metric" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/s-bit" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/up-down" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/subtlvs/subtlv/flags/state/flags" -} - -# Interfaces interface-id -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/interface-id" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/interface-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/interface-id" -} - -# Interfaces enabled -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/enabled" -} - -# Interfaces circuit-type -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/config/circuit-type" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/state/circuit-type" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/adj-changes" -} - -# Interfaces circuit-counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/adj-changes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/adj-number" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/id-field-len-mismatches" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/init-fails" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/lan-dis-changes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/max-area-address-mismatches" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/circuit-counters/state/rejected-adj" -} - -# Interfaces csnp interval -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/csnp-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/state/csnp-interval" -} - -# Interfaces lsp pacing interval -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/state/lsp-pacing-interval" -} - -# Interfaces afi name -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/afi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/afi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/state/afi-name" -} - -# Interfaces safi name -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/safi-name" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/state/safi-name" -} - -# Interfaces afi-safi enabled -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/state/enabled" -} - -# Interfaces level number -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/level-number" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/level-number" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/state/level-number" -} - -# Interfaces level enabled -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/state/enabled" -} - -# Interfaces hello timers interval -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/state/hello-interval" -} - -# Interfaces hello timers multiplier -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/config/hello-multiplier" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/timers/state/hello-multiplier" -} - -# Interfaces afi name -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/afi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name" -} - -# Interfaces safi name -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/safi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name" -} - -# Interfaces afi-safi enabled -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/enabled" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/enabled" -} - -# Interfaces afi-safi metric -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/metric" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric" -} - -# IPv6 Reachability -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/metric" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/s-bit" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/up-down" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/state/x-bit" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/subtlvs/subtlv/flags/state/flags" -} - -# Interface counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/retransmit" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/retransmit" -} - -# Network instance -config_path { - path: "/network-instances/network-instance/protocols/protocol/config/identifier" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/config/name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/state/identifier" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/state/name" -} - -# Global counters -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/config/lsp-lifetime-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/config/lsp-refresh-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/spf/config/spf-first-interval" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/spf/config/spf-hold-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/state/lsp-lifetime-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/state/lsp-refresh-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/spf/state/spf-first-interval" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/global/timers/spf/state/spf-hold-interval" - } - -# Adjacencies -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/area-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-circuit-type" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/state/system-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-snpa" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/nlpid" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/priority" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-status" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-support" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/restart-suppress" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/topology" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/up-timestamp" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv4-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/neighbor-ipv6-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/system-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/up-timestamp" -} - - -# AFI-SAFI -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/afi-name" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/metric" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/state/safi-name" -} - -# CSNP counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/dropped" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/processed" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/received" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/csnp/state/sent" -} - -# IIH counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/dropped" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/received" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/sent" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/iih/state/processed" -} - -# LSP Counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/dropped" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/processed" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/received" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/retransmit" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/lsp/state/sent" -} - -# PSNP Counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/dropped" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/processed" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/sent" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/packet-counters/psnp/state/received" -} - -# System Level Counters -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/exceed-max-seq-nums" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/manual-address-drop-from-areas" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/part-changes" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/total-lsps" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/corrupted-lsps" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/database-overloads" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/id-len-mismatch" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/lsp-errors" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/max-area-address-mismatches" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/own-lsp-purges" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/seq-num-skips" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/system-level-counters/state/spf-runs" -} - -feature_profile_dependency { - name: "networkinstance" - version: 1 -} - -feature_profile_dependency { - name: "interface_singleton" - version: 1 -} diff --git a/feature/isis/link_state_database/feature.textproto b/feature/isis/link_state_database/feature.textproto deleted file mode 100644 index bb823bb97d6..00000000000 --- a/feature/isis/link_state_database/feature.textproto +++ /dev/null @@ -1,159 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "isis_link_state_database" - version: 1 -} - -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/state/prefix" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/subtlvs/subtlv/ipv4-source-router-id/state/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/subtlvs/subtlv/ipv6-source-router-id/state/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/subtlvs/subtlv/prefix-sids/prefix-sid/state/algorithm" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/subtlvs/subtlv/prefix-sids/prefix-sid/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-ipv4-reachability/prefixes/prefix/subtlvs/subtlv/prefix-sids/prefix-sid/state/value" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/state/metric" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/adjacency-sids/adjacency-sid/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/adjacency-sids/adjacency-sid/state/value" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/adjacency-sids/adjacency-sid/state/weight" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/available-bandwidth/state/bandwidth" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/extended-admin-group/state/extended-admin-group" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/lan-adjacency-sids/lan-adjacency-sid/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/lan-adjacency-sids/lan-adjacency-sid/state/neighbor-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/lan-adjacency-sids/lan-adjacency-sid/state/value" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/lan-adjacency-sids/lan-adjacency-sid/state/weight" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/link-attributes/state/local-protection" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/max-link-bandwidth/state/bandwidth" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/max-reservable-link-bandwidth/state/bandwidth" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/residual-bandwidth/state/bandwidth" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/unreserved-bandwidth/setup-priority/state/bandwidth" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/extended-is-reachability/neighbors/neighbor/instances/instance/subtlvs/subtlv/utilized-bandwidth/state/bandwidth" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-interface-addresses/state/address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-srlgs/ipv4-srlg/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-srlgs/ipv4-srlg/state/ipv4-interface-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-srlgs/ipv4-srlg/state/ipv4-neighbor-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-srlgs/ipv4-srlg/state/psn-number" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-srlgs/ipv4-srlg/state/srlg-value" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-srlgs/ipv4-srlg/state/system-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv4-te-router-id/state/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-interface-addresses/state/address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/subtlvs/subtlv/prefix-sids/prefix-sid/state/algorithm" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/subtlvs/subtlv/prefix-sids/prefix-sid/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-reachability/prefixes/prefix/subtlvs/subtlv/prefix-sids/prefix-sid/state/value" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-srlgs/ipv6-srlg/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-srlgs/ipv6-srlg/state/ipv6-interface-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-srlgs/ipv6-srlg/state/ipv6-neighbor-address" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-srlgs/ipv6-srlg/state/psn-number" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-srlgs/ipv6-srlg/state/srlg-value" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-srlgs/ipv6-srlg/state/system-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/ipv6-te-router-id/state/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/mt-ipv6-reachability/prefixes/prefix/subtlvs/subtlv/ipv4-source-router-id/state/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/mt-ipv6-reachability/prefixes/prefix/subtlvs/subtlv/ipv6-source-router-id/state/router-id" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/router-capabilities/capability/subtlvs/subtlv/segment-routing-capability/state/flags" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/router-capabilities/capability/subtlvs/subtlv/segment-routing-capability/srgb-descriptors/srgb-descriptor/state/label" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv/router-capabilities/capability/subtlvs/subtlv/segment-routing-capability/srgb-descriptors/srgb-descriptor/state/range" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/flags" -} - -feature_profile_dependency { - name: "isis" - version: 1 -} - -feature_profile_dependency { - name: "networkinstance" - version: 1 -} diff --git a/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go b/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go index d3d5fc5f49e..e4666dd2acb 100644 --- a/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go +++ b/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go @@ -102,13 +102,6 @@ func TestBasic(t *testing.T) { check.Equal(isisRoot.Global().Af(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled().State(), true)) } - // if ISISInterfaceLevel1DisableRequired is set, validate Level1 enabled false at interface level else validate Level2 enabled at global level - if deviations.ISISInterfaceLevel1DisableRequired(ts.DUT) { - checks = append(checks, check.Equal(port1ISIS.Level(1).Enabled().State(), false)) - } else { - checks = append(checks, check.Equal(isisRoot.Level(2).Enabled().State(), true)) - } - for _, vd := range checks { t.Run(vd.RelPath(isisRoot), func(t *testing.T) { if err := vd.AwaitUntil(deadline, ts.DUTClient); err != nil { @@ -703,11 +696,10 @@ func TestISISHelloTimer(t *testing.T) { intf := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)). GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isissession.ISISName). GetOrCreateIsis().GetOrCreateInterface(intfName) - if !deviations.ISISInterfaceLevel1DisableRequired(ts.DUT) { - timers1 := intf.GetOrCreateLevel(uint8(1)).GetOrCreateTimers() - timers1.SetHelloInterval(tc.helloInterval) - timers1.SetHelloMultiplier(tc.helloMultiplier) - } + + timers1 := intf.GetOrCreateLevel(uint8(1)).GetOrCreateTimers() + timers1.SetHelloInterval(tc.helloInterval) + timers1.SetHelloMultiplier(tc.helloMultiplier) intfLeveL2 := intf.GetOrCreateLevel(uint8(level2)) intfLeveL2.Enabled = ygot.Bool(true) diff --git a/feature/isis/otg_tests/base_adjacencies_test/metadata.textproto b/feature/isis/otg_tests/base_adjacencies_test/metadata.textproto index 342b98422cb..9aafcad1f2e 100644 --- a/feature/isis/otg_tests/base_adjacencies_test/metadata.textproto +++ b/feature/isis/otg_tests/base_adjacencies_test/metadata.textproto @@ -25,7 +25,6 @@ platform_exceptions: { vendor: CISCO } deviations: { - isis_interface_level1_disable_required: true isis_single_topology_required: true set_isis_auth_with_interface_authentication_container: true } @@ -41,6 +40,7 @@ platform_exceptions: { default_network_instance: "default" isis_instance_enabled_required: true isis_interface_afi_unsupported: true + fr_breakout_fix: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/graceful_restart_restarting/README.md b/feature/isis/otg_tests/graceful_restart/README.md similarity index 93% rename from feature/isis/otg_tests/graceful_restart_restarting/README.md rename to feature/isis/otg_tests/graceful_restart/README.md index fb50a29258c..634151ad276 100644 --- a/feature/isis/otg_tests/graceful_restart_restarting/README.md +++ b/feature/isis/otg_tests/graceful_restart/README.md @@ -1,8 +1,8 @@ -# RT-2.17: IS-IS Graceful Restart Restarting +# RT-2.16: IS-IS Graceful Restart Helper ## Summary -- test verify isis garceful restarts support restarter mode. +- test verifies isis garceful restart support in helper and restarter role ## Testbed type @@ -187,8 +187,7 @@ } ``` * Enable IPv4 and IPv6 IS-IS L2 adjacency between ATE port-1 and DUT port-1, DUT port-2 and ATE port-2 in point-to-point mode.\ - * Enable GR helper on ATE port-1 na ATE port-2 in compliacnce with RFC5306 (non-planned restart ONLY). - * Set ISIS graceful restart helper mode on DUT + * Set ISIS graceful restart helper mode on DUT ```json { @@ -206,7 +205,7 @@ "graceful-restart": { "config": { "enabled": true, - "helper-only": false, + "helper-only": true, "restart-time": 30 } } @@ -221,8 +220,16 @@ } ``` -### RT-2.17.1 CONTROLLER-CARD switchover [TODO: ] -#### +### RT-2.16.1 GR Helper: [TODO: https://github.com/openconfig/featureprofiles/issues/2494] + +* Generate traffic form ATE port-1 to "target IPv4" and "target IPv6" networks (ATE port-2) +* Verify traffic is recived on ATE port-2 +* Restart ISIS on ATE port-2 (Alternativly: using set_control_state to "down" for emulated isis router. Wait (restart-time - 10) sec and set it back to "up") +* Verify traffic is recived on ATE port-2 during restart time ( no losses ) +* Disable ISIS on ATE port-2 (set_control_state to "down"). Wait restart-time seconds +* Verify traffic is NOT recived on ATE port-2 (after restart-time expires) + +### RT-2.16.2 CONTROLLER-CARD switchover: * Generate traffic form ATE port-1 to "target IPv4" and "target IPv6" networks (ATE port-2) * Verify traffic is recived on ATE port-2 @@ -230,7 +237,8 @@ * Verify traffic is recived on ATE port-2 during restart time ( no losses ) * Wait 60 sec. -### RT-2.17.2 DUT ISIS restart [TODO: ] +### RT-2.16.3 DUT ISIS restart: + * Generate traffic form ATE port-1 to "target IPv4" and "target IPv6" networks (ATE port-2) * Verify traffic is recived on ATE port-2 * Using gNOI KillProcess w/ SIGNAL_KILL call restart process serving ISIS (implementation dependednt). This try to simulate ISIS crash due to unexpected error. @@ -259,4 +267,5 @@ rpcs: ## Required DUT platform -* FFF +* MFF + diff --git a/feature/isis/otg_tests/graceful_restart/graceful_restart_test.go b/feature/isis/otg_tests/graceful_restart/graceful_restart_test.go new file mode 100644 index 00000000000..20d9e242662 --- /dev/null +++ b/feature/isis/otg_tests/graceful_restart/graceful_restart_test.go @@ -0,0 +1,613 @@ +package graceful_restart_test + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/args" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/components" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/gnoi" + "github.com/openconfig/featureprofiles/internal/helpers" + otgvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_validation_helpers" + "github.com/openconfig/featureprofiles/internal/otgutils" + spb "github.com/openconfig/gnoi/system" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/testt" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + ipv4NetPfxLen = 24 + ipv6NetPfxLen = 64 + isisSysID1 = "640000000001" + isisSysID2 = "640000000002" + isisAreaAddr = "49.0001" + dutSysID = "1920.0000.2001" + isisMetric = 10 + gracefulRestartTime = 30 + restartWait = 40 + trafficPps = 100 + trafficFrameSize = 512 + trafficDuration = time.Duration(trafficFrameSize / trafficPps) + lossTolerancePct = 2 + v4FlowName = "ipv4_flow" + v6FlowName = "ipv6_flow" + isisInstance = "DEFAULT" + isisPort1Device = "dev1Isis" + isisPort2Device = "dev2Isis" + isisLevel = 2 + sleepTime = 10 * time.Second + + controlcardType = oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_CONTROLLER_CARD + activeController = oc.Platform_ComponentRedundantRole_PRIMARY + standbyController = oc.Platform_ComponentRedundantRole_SECONDARY + maxSwitchoverTime = 900 +) + +var ( + // DUT port attributes + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE Port1", + IPv4: "192.168.1.1", + IPv6: "2001:DB8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE Port2", + IPv4: "192.168.1.5", + IPv6: "2001:DB8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + // ATE port attributes + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.168.1.2", + IPv6: "2001:DB8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:02:01:01:01", + IPv4: "192.168.1.6", + IPv6: "2001:DB8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + // Advertised networks from ATE port-2 + ipv4Prefix = "192.168.10.0" + ipv6Prefix = "2024:db8:128:128::" + + port2isis gosnappi.DeviceIsisRouter +) + +type isisConfig struct { + port string + level oc.E_Isis_LevelType +} + +// TestMain is the entry point for the test suite. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dc := gnmi.OC() + + // Configure interfaces on DUT Port 1 + p1 := dut.Port(t, "port1") + gnmi.Replace(t, dut, dc.Interface(p1.Name()).Config(), configInterfaceDUT(p1, &dutPort1, dut)) + + // Configure interfaces on DUT Port 2 + p2 := dut.Port(t, "port2") + gnmi.Replace(t, dut, dc.Interface(p2.Name()).Config(), configInterfaceDUT(p2, &dutPort2, dut)) + + // Configure IS-IS Protocol + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + isisProtocol := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance) + + isisProtocol.SetEnabled(true) + isis := isisProtocol.GetOrCreateIsis() + + globalISIS := isis.GetOrCreateGlobal() + if deviations.ISISInstanceEnabledRequired(dut) { + globalISIS.SetInstance(isisInstance) + } + + // Configure Global ISIS settings + globalISIS.SetNet([]string{fmt.Sprintf("%s.%s.00", isisAreaAddr, dutSysID)}) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).SetEnabled(true) + globalISIS.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).SetEnabled(true) + level := isis.GetOrCreateLevel(isisLevel) + level.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + // Configure ISIS enabled flag at level + if deviations.ISISLevelEnabled(dut) { + level.SetEnabled(true) + } + + isisGracefulRestart := globalISIS.GetOrCreateGracefulRestart() + isisGracefulRestart.SetEnabled(true) + isisGracefulRestart.SetHelperOnly(true) + isisGracefulRestart.SetRestartTime(gracefulRestartTime) + + isisConf := []*isisConfig{ + {port: p1.Name(), level: oc.Isis_LevelType_LEVEL_2}, + {port: p2.Name(), level: oc.Isis_LevelType_LEVEL_2}, + } + + for _, isisPort := range isisConf { + // Configure ISIS on DUT Port 1 + intf := isis.GetOrCreateInterface(isisPort.port) + intf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + intf.SetEnabled(true) + // Configure ISIS level at global mode if true else at interface mode + if deviations.ISISInterfaceLevel1DisableRequired(dut) { + intf.GetOrCreateLevel(1).SetEnabled(false) + } else { + intf.GetOrCreateLevel(2).SetEnabled(true) + } + globalISIS.LevelCapability = isisPort.level + // Configure ISIS enable flag at interface level + intf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).SetEnabled(true) + intf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).SetEnabled(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + intf.Af = nil + } + } + + // Push ISIS configuration to DUT + gnmi.Replace(t, dut, dc.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Config(), isisProtocol) +} + +func configInterfaceDUT(p *ondatra.Port, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + i := a.NewOCInterface(p.Name(), dut) + s4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.SetEnabled(true) + } + i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + + return i +} + +func configureOTG(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + otgConfig := gosnappi.NewConfig() + + port1 := otgConfig.Ports().Add().SetName("port1") + port2 := otgConfig.Ports().Add().SetName("port2") + + port1Dev := otgConfig.Devices().Add().SetName(atePort1.Name + ".dev") + port1Eth := port1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + port1Eth.Connection().SetPortName(port1.Name()) + port1Ipv4 := port1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + port1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + port1Ipv6 := port1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + port1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + // Add IS-IS in ATE port1 + port1isis := port1Dev.Isis().SetSystemId(isisSysID1).SetName(isisPort1Device) + + port1isis.Basic().SetIpv4TeRouterId(atePort1.IPv4) + port1isis.Basic().SetHostname(port1isis.Name()) + port1isis.Basic().SetEnableWideMetric(true) + port1isis.Basic().SetLearnedLspFilter(true) + + devIsisport1 := port1isis.Interfaces().Add().SetEthName(port1Dev.Ethernets().Items()[0].Name()). + SetName("devIsisPort1").SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_1_2).SetMetric(10) + + devIsisport1.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + port2Dev := otgConfig.Devices().Add().SetName(atePort2.Name + ".dev") + port2Eth := port2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + port2Eth.Connection().SetPortName(port2.Name()) + port2Ipv4 := port2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + port2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + port2Ipv6 := port2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + port2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + // Add IS-IS in ATE port2 + port2isis = port2Dev.Isis().SetSystemId(isisSysID2).SetName(isisPort2Device) + + port2isis.Basic().SetIpv4TeRouterId(atePort2.IPv4) + port2isis.Basic().SetHostname(port2isis.Name()) + port2isis.Basic().SetEnableWideMetric(true) + port2isis.Basic().SetLearnedLspFilter(true) + port2isis.GracefulRestart().SetHelperMode(false) + + devIsisport2 := port2isis.Interfaces().Add().SetEthName(port2Dev.Ethernets().Items()[0].Name()). + SetName("devIsisPort2").SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_1_2).SetMetric(10) + + devIsisport2.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + // Advertise IPv4 network from ATE Port 2's ISIS router + isis2Net4 := port2isis.V4Routes().Add().SetName("ipv4-network") + isis2Net4.SetLinkMetric(isisMetric) + isis2Net4.Addresses().Add().SetAddress(ipv4Prefix).SetPrefix(ipv4NetPfxLen) + + // Advertise IPv6 network from ATE Port 2's ISIS router + isis2Net6 := port2isis.V6Routes().Add().SetName("ipv6-network") + isis2Net6.SetLinkMetric(isisMetric) + isis2Net6.Addresses().Add().SetAddress(ipv6Prefix).SetPrefix(ipv6NetPfxLen) + + return otgConfig +} + +func createTrafficFlows(t *testing.T, otgConfig gosnappi.Config, flowNameV4, flowNameV6 string) { + t.Helper() + // IPv4 flow from ATE port-1 to the advertised network on ATE port-2 + v4Flow := otgConfig.Flows().Add().SetName(flowNameV4) + v4Flow.Metrics().SetEnable(true) + v4Flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv4"}).SetRxNames([]string{"ipv4-network"}) + v4Flow.Rate().SetPps(trafficPps) + v4Flow.Size().SetFixed(trafficFrameSize) + e1 := v4Flow.Packet().Add().Ethernet() + e1.Src().SetValue(atePort1.MAC) + e1.Dst().Auto() + v4 := v4Flow.Packet().Add().Ipv4() + v4.Src().SetValue(atePort1.IPv4) + v4.Dst().SetValue(ipv4Prefix) + + // IPv6 flow from ATE port-1 to the advertised network on ATE port-2 + v6Flow := otgConfig.Flows().Add().SetName(flowNameV6) + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{atePort1.Name + ".IPv6"}). + SetRxNames([]string{"ipv6-network"}) + v6Flow.Rate().SetPps(trafficPps) + v6Flow.Size().SetFixed(trafficFrameSize) + e2 := v6Flow.Packet().Add().Ethernet() + e2.Src().SetValue(atePort1.MAC) + e2.Dst().Auto() + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(atePort1.IPv6) + v6.Dst().SetValue(ipv6Prefix) +} + +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() + for _, intfName := range dutIntf { + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + intfName = intfName + ".0" + } + nbrPath := statePath.Interface(intfName) + query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, 5*time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + state, present := val.Val() + if present && state == oc.Isis_IsisInterfaceAdjState_UP { + t.Logf("IS-IS state on %v has adjacencies", intfName) + } + return true + }).Await(t) + if !ok { + t.Logf("IS-IS state on %v has no adjacencies", intfName) + t.Fatal("No IS-IS adjacencies reported.") + } + } +} + +// verifyTraffic checks traffic flow metrics against expected loss. +func verifyTraffic(t *testing.T, otg *otg.OTG, otgConfig gosnappi.Config, expectLoss bool) bool { + t.Helper() + + fail := false + + otgutils.LogFlowMetrics(t, otg, otgConfig) + otgutils.LogPortMetrics(t, otg, otgConfig) + + for _, flowName := range []string{v4FlowName, v6FlowName} { + metrics := gnmi.Get(t, otg, gnmi.OTG().Flow(flowName).State()) + txPackets := metrics.GetCounters().GetOutPkts() + rxPackets := metrics.GetCounters().GetInPkts() + + if txPackets == 0 { + t.Fatalf("Transmit packets for flow %s was 0, expected > 0", flowName) + } + + lossPct := (float64(txPackets-rxPackets) / float64(txPackets)) * 100 + + switch { + case expectLoss && lossPct < (100-lossTolerancePct): + t.Errorf("traffic loss for flow %s was less than expected: got %v, want > %v", flowName, lossPct, 100-lossTolerancePct) + fail = true + + case expectLoss: + t.Logf("Traffic loss for flow %s was as expected: %v", flowName, lossPct) + + case lossPct > lossTolerancePct: + t.Errorf("traffic loss for flow %s was higher than expected: got %v, want < %v", flowName, lossPct, lossTolerancePct) + fail = true + + default: + t.Logf("No loss seen for flow %s as expected", flowName) + } + } + + return fail +} + +func startStopISISRouter(t *testing.T, otg *otg.OTG, routeNames []string, state string) { + cs := gosnappi.NewControlState() + route := cs.Protocol().Isis().Routers().SetRouterNames(routeNames) + switch state { + case "DOWN": + route.SetState(gosnappi.StateProtocolIsisRoutersState.DOWN) + case "UP": + route.SetState(gosnappi.StateProtocolIsisRoutersState.UP) + default: + t.Error("invalid state for action to be performed on ISIS router") + } + + otg.SetControlState(t, cs) +} + +func TestGracefulRestart(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + configureDUT(t, dut) + + otgConfig := configureOTG(t, ate) + createTrafficFlows(t, otgConfig, v4FlowName, v6FlowName) + ate.OTG().PushConfig(t, otgConfig) + ate.OTG().StartProtocols(t) + time.Sleep(20 * time.Second) + verifyISISTelemetry(t, dut, []string{dut.Port(t, "port1").Name(), dut.Port(t, "port2").Name()}) + + type testCase struct { + Name string + Description string + testFunc func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, otgConfig gosnappi.Config) + } + + testCases := []testCase{ + { + Name: "Testcase: ISIS with GR Helper", + Description: "Validate traffic with GR enabled", + testFunc: testGrHelper, + }, + { + Name: "Testcase: ISIS with Controller Card Switchover", + Description: "Validate traffic with controller card switchover", + testFunc: testISISWithControllerCardSwitchOver, + }, + { + Name: "Testcase: Verify traffic with DUT ISIS Restart", + Description: "Validate traffic with DUT ISIS restart", + testFunc: testISISWithDUTRestart, + }, + } + + // Run the test cases. + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + t.Logf("Description: %s", tc.Description) + tc.testFunc(t, dut, ate, otgConfig) + }) + } +} + +func testGrHelper(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, otgConfig gosnappi.Config) { + otg := ate.OTG() + + var expectedISISAdj = map[string]interface{}{ + "IsisRouterName": isisPort2Device, + "LocalStateTypeExp": "LEVEL_1_2", + "LocalStateHoldTimeExp": uint32(30), + "LocalStateRestartStatusExp": "RUNNING", + "LocalStateLastAttemptExp": "SUCCEEDED", + "NeighborStateTypeExp": "LEVEL_2", + "NeighborStateHoldTimeExp": uint32(30), + "NeighborStateRestartStatusExp": "RUNNING", + "NeighborStateLastAttemptExp": "UNAVAILABLE", + } + + // Validating subtest 1: Ipv4/Ipv6 traffic from ATE port1 to port2 + t.Logf("Subtest-1: Verify traffic from ATE port1 to \"target IPv4\" and \"target IPv6\" networks on ATE port-2") + otg.StartTraffic(t) + time.Sleep(sleepTime) + otg.StopTraffic(t) + if verifyTraffic(t, otg, otgConfig, false) { + t.Error("traffic loss for flow is more than expected") + } + + // Validating subtest 2: Restarting IS-IS on ATE port-2 and verifying traffic is not lost due to GR + t.Logf("Subtest-2: Restarting IS-IS on ATE port-2 and verifying traffic is not lost due to GR.") + cs := gosnappi.NewControlAction() + isisRestart := cs.Protocol().Isis().InitiateGracefulRestart().SetRouterNames([]string{isisPort2Device}) + isisRestart.Unplanned().SetHoldingTime(gracefulRestartTime).SetRestartAfter(uint32(restartWait)) + startTime := time.Now() + + // Initiating graceful restart, waiting for ISIS to come up after GR time expiry and validating traffic + otg.StartTraffic(t) + replaceDuration := time.Since(startTime) + t.Log("Send traffic while GR timer is counting down. Traffic should pass as ISIS GR is enabled!") + otg.SetControlAction(t, cs) + sleepTime := gracefulRestartTime*time.Second - replaceDuration + time.Sleep(sleepTime) + otg.StopTraffic(t) + if verifyTraffic(t, otg, otgConfig, false) { + t.Error("traffic loss for flow is more than expected") + } + + otgvalidationhelpers.ValidateOTGISISTelemetry(t, ate, expectedISISAdj) + + time.Sleep(sleepTime) + t.Log("Verify ISIS is up again after GR timeout expiry") + verifyISISTelemetry(t, dut, []string{dut.Port(t, "port1").Name(), dut.Port(t, "port2").Name()}) + + // Initiating graceful restart, validating traffic loss after graceful restart expires before restart time + t.Logf("Validating traffic loss after after Restart Time expiry") + otg.SetControlAction(t, cs) + + //The graceful restart timer is set to 30, validating traffic as soon as it expires before it initiate restart + time.Sleep(29 * time.Second) + otg.StartTraffic(t) + time.Sleep(5 * time.Second) + otg.StopTraffic(t) + if verifyTraffic(t, otg, otgConfig, true) { + t.Error("traffic loss is not seen for flow as expected") + } + + // Validating subtest 3: Disable IS-IS on ATE port-2 and verifying traffic is lost due to GR. + t.Logf("Subtest-3: Disable IS-IS on ATE port-2 and verifying traffic is lost due to GR.") + otg.SetControlAction(t, cs) + startStopISISRouter(t, otg, []string{isisPort2Device}, "DOWN") + time.Sleep(restartWait * time.Second) + otg.StartTraffic(t) + time.Sleep(sleepTime) + otg.StopTraffic(t) + if verifyTraffic(t, otg, otgConfig, true) { + t.Error("traffic loss is not seen for flow as expected") + } +} + +func testISISWithControllerCardSwitchOver(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, otgConfig gosnappi.Config) { + + otg := ate.OTG() + + otg.StartProtocols(t) + verifyISISTelemetry(t, dut, []string{dut.Port(t, "port1").Name(), dut.Port(t, "port2").Name()}) + + otg.StartTraffic(t) + time.Sleep(sleepTime) + otg.StopTraffic(t) + if verifyTraffic(t, otg, otgConfig, false) { + t.Error("traffic loss for flow is more than expected") + } + + // TODO: Not able to verify because of HW limitation. Adding the below deviation instead creating new one + if deviations.GNOISwitchoverReasonMissingUserInitiated(dut) { + // TODO: Not able to verify because of HW limitation. Adding the below deviation instead creating new one + } else { + t.Logf("Initiating controller card switchover") + + controllerCards := components.FindComponentsByType(t, dut, controlcardType) + t.Logf("Found controller card list: %v", controllerCards) + + if got, want := len(controllerCards), 2; got < want { + t.Skipf("Not enough controller cards for the test on %v: got %v, want at least %v", dut.Model(), got, want) + } + + rpStandbyBeforeSwitch, rpActiveBeforeSwitch := components.FindStandbyControllerCard(t, dut, controllerCards) + t.Logf("Detected rpStandby: %v, rpActive: %v", rpStandbyBeforeSwitch, rpActiveBeforeSwitch) + + switchoverReady := gnmi.OC().Component(rpActiveBeforeSwitch).SwitchoverReady() + gnmi.Await(t, dut, switchoverReady.State(), 30*time.Minute, true) + t.Logf("SwitchoverReady().Get(t): %v", gnmi.Get(t, dut, switchoverReady.State())) + if got, want := gnmi.Get(t, dut, switchoverReady.State()), true; got != want { + t.Errorf("switchoverReady.Get(t): got %v, want %v", got, want) + } + + intfsOperStatusUPBeforeSwitch := helpers.FetchOperStatusUPIntfs(t, dut, *args.CheckInterfacesInBinding) + t.Logf("intfsOperStatusUP interfaces before switchover: %v", intfsOperStatusUPBeforeSwitch) + if got, want := len(intfsOperStatusUPBeforeSwitch), 0; got == want { + t.Errorf("get the number of intfsOperStatusUP interfaces for %q: got %v, want > %v", dut.Name(), got, want) + } + + gnoiClient := dut.RawAPIs().GNOI(t) + useNameOnly := deviations.GNOISubcomponentPath(dut) + switchoverRequest := &spb.SwitchControlProcessorRequest{ + ControlProcessor: components.GetSubcomponentPath(rpStandbyBeforeSwitch, useNameOnly), + } + t.Logf("switchoverRequest: %v", switchoverRequest) + switchoverResponse, err := gnoiClient.System().SwitchControlProcessor(context.Background(), switchoverRequest) + if err != nil { + t.Fatalf("Failed to perform control processor switchover with unexpected err: %v", err) + } + t.Logf("gnoiClient.System().SwitchControlProcessor() response: %v, err: %v", switchoverResponse, err) + + startSwitchover := time.Now() + t.Logf("Wait for new active RP to boot up by polling the telemetry output.") + for { + var currentTime string + t.Logf("Time elapsed %.2f seconds since switchover started.", time.Since(startSwitchover).Seconds()) + if !verifyTraffic(t, otg, otgConfig, false) { + break + } + time.Sleep(60 * time.Second) + if errMsg := testt.CaptureFatal(t, func(t testing.TB) { + currentTime = gnmi.Get(t, dut, gnmi.OC().System().CurrentDatetime().State()) + }); errMsg != nil { + t.Logf("Got testt.CaptureFatal errMsg: %s, keep polling ...", *errMsg) + } else { + t.Logf("RP switchover has completed successfully with received time: %v", currentTime) + break + } + + if got, want := uint64(time.Since(startSwitchover).Seconds()), uint64(maxSwitchoverTime); got >= want { + t.Fatalf("time.Since(startSwitchover): got %v, want < %v", got, want) + } + } + } + +} + +func testISISWithDUTRestart(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, otgConfig gosnappi.Config) { + + otg := ate.OTG() + + var expectedISISAdj = map[string]interface{}{ + "IsisRouterName": isisPort2Device, + "LocalStateTypeExp": "LEVEL_1_2", + "LocalStateHoldTimeExp": uint32(30), + "LocalStateRestartStatusExp": "RUNNING", + "LocalStateLastAttemptExp": "UNAVAILABLE", + "NeighborStateTypeExp": "LEVEL_2", + "NeighborStateHoldTimeExp": uint32(30), + "NeighborStateRestartStatusExp": "RUNNING", + "NeighborStateLastAttemptExp": "SUCCEEDED", + } + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() + gnmi.Update(t, dut, dutConfPath.Global().GracefulRestart().HelperOnly().Config(), false) + port2isis.GracefulRestart().SetHelperMode(true) + otg.PushConfig(t, otgConfig) + otg.StartProtocols(t) + time.Sleep(20 * time.Second) + + verifyISISTelemetry(t, dut, []string{dut.Port(t, "port1").Name(), dut.Port(t, "port2").Name()}) + otg.StartTraffic(t) + time.Sleep(sleepTime) + otg.StopTraffic(t) + if verifyTraffic(t, otg, otgConfig, false) { + t.Error("traffic loss for flow is more than expected") + } + + t.Logf("Initiating Kill Process on DUT") + gnoi.KillProcess(t, dut, "ISIS", gnoi.SigTerm, true, false) + startTime := time.Now() + for { + otg.StartTraffic(t) + time.Sleep(5 * time.Second) + otg.StopTraffic(t) + if !verifyTraffic(t, otg, otgConfig, false) { + break + } + + if uint64(time.Since(startTime).Seconds()) > gracefulRestartTime { + t.Fatalf("Traffic verification failed. Traffic didn't pass within the graceful restart time : %v sec", gracefulRestartTime) + } + } + + otgvalidationhelpers.ValidateOTGISISTelemetry(t, ate, expectedISISAdj) +} diff --git a/feature/isis/otg_tests/graceful_restart/metadata.textproto b/feature/isis/otg_tests/graceful_restart/metadata.textproto new file mode 100644 index 00000000000..1957bfb5883 --- /dev/null +++ b/feature/isis/otg_tests/graceful_restart/metadata.textproto @@ -0,0 +1,23 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "e7b4a72b-d2b1-4707-a7f0-0aee1c724b97" +plan_id: "RT-2.16" +description: "IS-IS Graceful Restart Helper" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + isis_instance_enabled_required: true + omit_l2_mtu: true + missing_value_for_defaults: true + interface_enabled: true + default_network_instance: "default" + isis_interface_afi_unsupported: true + gnoi_subcomponent_path: true + gnoi_switchover_reason_missing_user_initiated: true + } +} + diff --git a/feature/isis/otg_tests/graceful_restart_helper/README.md b/feature/isis/otg_tests/graceful_restart_helper/README.md deleted file mode 100644 index 63d327c91c3..00000000000 --- a/feature/isis/otg_tests/graceful_restart_helper/README.md +++ /dev/null @@ -1,252 +0,0 @@ -# RT-2.16: IS-IS Graceful Restart Helper - -## Summary - -- test verify isis garceful restarts support in helper role - -## Testbed type - -* https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed - -## Procedure - -#### Initial Setup: - -* Connect: - * DUT port-1 to ATE port-1 - * DUT port-2 to ATE port-2 - -* Configure IPv4 and IPv6 addresses on DUT and ATE ports as shown below - * DUT port-1 IPv4 address ```dp1-v4 = 192.168.1.1/30``` - * ATE port-1 IPv4 address ```ap1-v4 = 192.168.1.2/30``` - - * DUT port-2 IPv4 address ```dp2-v4 = 192.168.1.5/30``` - * ATE port-2 IPv4 address ```ap2-v4 = 192.168.1.6/30``` - - * DUT port-1 IPv6 address ```dp1-v6 = 2001:DB8::1/126``` - * ATE port-1 IPv6 address ```ap1-v6 = 2001:DB8::2/126``` - - * DUT port-2 IPv6 address ```dp2-v6 = 2001:DB8::5/126``` - * ATE port-2 IPv6 address ```ap2-v6 = 2001:DB8::6/126``` - -* Create an "target IPv4" network i.e. ```ipv4-network = 192.168.10.0/24``` attached to ATE port-2 and inject it to ISIS. - -* Create an "target IPv6" network i.e. ```ipv6-network = 2024:db8:128:128::/64``` attached to ATE port-2 and inject it to ISIS. - -* Configure ISIS - * Configure separate ISIS emulated routers, one on each ATE ports-1, port-2 - * Enable IPv4 and IPv6 IS-IS L2 adjacency between ATE port-1 and DUT port-1, DUT port-2 and ATE port-2 in point-to-point mode. - - ```json - { - "network-instances": { - "network-instance": [ - { - "name": "DEFAULT", - "protocols": { - "protocol": [ - { - "identifier": "ISIS", - "name": "DEFAULT", - "config": { - "name": "DEFAULT", - "identifier": "ISIS" - }, - "isis": { - "global": { - "afi-safi": { - "af": [ - { - "afi-name": "IPV4", - "config": { - "afi-name": "IPV4", - "enabled": true, - "safi-name": "UNICAST" - }, - "safi-name": "UNICAST" - }, - { - "afi-name": "IPV6", - "config": { - "afi-name": "IPV6", - "enabled": true, - "safi-name": "UNICAST" - }, - "safi-name": "UNICAST" - } - ] - }, - "config": { - "level-capability": "LEVEL_2", - "net": [ - "" - ] - } - }, - "interfaces": { - "interface": [ - { - "config": { - "passive": true, - "enabled": true, - "interface-id": "Loopback0" - }, - "interface-id": "Loopback0", - "interface-ref": { - "config": { - "interface": "loopback0", - "subinterface": 0 - } - }, - "levels": { - "level": [ - { - "config": { - "level-number": 2, - "enabled": true - }, - "level-number": 2 - } - ] - } - }, - { - "config": { - "circuit-type": "POINT_TO_POINT", - "enabled": true, - "interface-id": "" - }, - "interface-id": "", - "interface-ref": { - "config": { - "interface": "", - "subinterface": 0 - } - }, - "levels": { - "level": [ - { - "afi-safi": { - "af": [ - { - "afi-name": "IPV4", - "config": { - "afi-name": "IPV4", - "metric": 10, - "safi-name": "UNICAST" - }, - "safi-name": "UNICAST" - }, - { - "afi-name": "IPV6", - "config": { - "afi-name": "IPV6", - "metric": 10, - "safi-name": "UNICAST" - }, - "safi-name": "UNICAST" - } - ] - }, - "config": { - "level-number": 2, - "enabled": true - }, - "level-number": 2, - "timers": { - "config": { - "hello-interval": 10, - "hello-multiplier": 6 - } - } - } - ] - } - } - ] - }, - "levels": { - "level": [ - { - "config": { - "level-number": 2, - "metric-style": "WIDE_METRIC", - "enabled": true - }, - "level-number": 2 - } - ] - } - } - } - ] - } - } - ] - } - } - ``` - * Enable IPv4 and IPv6 IS-IS L2 adjacency between ATE port-1 and DUT port-1, DUT port-2 and ATE port-2 in point-to-point mode.\ - * Set ISIS graceful restart helper mode on DUT - - ```json - { - "network-instances": { - "network-instance": [ - { - "name": "DEFAULT", - "protocols": { - "protocol": [ - { - "identifier": "ISIS", - "name": "DEFAULT", - "isis": { - "global": { - "graceful-restart": { - "config": { - "enabled": true, - "helper-only": true, - "restart-time": 30 - } - } - } - } - } - ] - } - } - ] - } - } - ``` - -### RT-2.16.1 [TODO: https://github.com/openconfig/featureprofiles/issues/2494] -#### GR helper - -* Generate traffic form ATE port-1 to "target IPv4" and "target IPv6" networks (ATE port-2) -* Verify traffic is recived on ATE port-2 -* Restart ISIS on ATE port-2 (Alternativly: using set_control_state to "down" for emulated isis router. Wait (restart-time - 10) sec and set it back to "up") -* Verify traffic is recived on ATE port-2 during restart time ( no losses ) -* Disable ISIS on ATE port-2 (set_control_state to "down"). Wait restart-time seconds -* Verify traffic is NOT recived on ATE port-2 (after restart-time expires) - -## 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 ## - /network-instances/network-instance/protocols/protocol/isis/global/graceful-restart/config/enabled: - /network-instances/network-instance/protocols/protocol/isis/global/graceful-restart/config/helper-only: - /network-instances/network-instance/protocols/protocol/isis/global/graceful-restart/config/restart-time: - -rpcs: - gnmi: - gNMI.Subscribe: - gNMI.Set: -``` - -## Required DUT platform - -* FFF \ No newline at end of file diff --git a/feature/isis/otg_tests/isis_drain_test/README.md b/feature/isis/otg_tests/isis_drain_test/README.md index 67410a03579..e152f6b1ba7 100644 --- a/feature/isis/otg_tests/isis_drain_test/README.md +++ b/feature/isis/otg_tests/isis_drain_test/README.md @@ -4,13 +4,19 @@ Ensure that IS-IS metric change can drain traffic from a DUT trunk interface +## Canonical OC +```json +{} +``` + ## Procedure * Connect three ATE ports to the DUT * Port-2 and port-3 each makes a one-member trunk port with the same ISIS metric 10 configured for the trunk interfaces (trunk-2 and trunk-3). * Configure a destination network-a connected to trunk-2 and trunk-3. +* Verify that IPv4 prefix advertised by ATE is correctly installed into DUTs forwarding table. * Send 10K IPv4 traffic flows from ATE port-1 to network-a. Validate that traffic is going via trunk-2 and trunk-3 and there is no traffic loss -* Change the ISIS metric of trunk-2 to 1000 value. Validate that 100% of the traffic is going out of only trunk-3 and there is no traffic loss. -* Revert back the ISIS metric on trunk-2. Validate that the traffic is going via both trunk-2 and trunk-3, and there is no traffic loss. +* Change the ISIS metric of trunk-2 to 1000 value. Verify prefix is correctly installed into DUTs forwarding table.Validate that 100% of the traffic is going out of only trunk-3 and there is no traffic loss. +* Revert back the ISIS metric on trunk-2. Verify prefix is correctly installed into DUTs forwarding table.Validate that the traffic is going via both trunk-2 and trunk-3, and there is no traffic loss. ## OpenConfig Path and RPC Coverage ```yaml diff --git a/feature/isis/otg_tests/isis_drain_test/isis_drain_test.go b/feature/isis/otg_tests/isis_drain_test/isis_drain_test.go index 0aa38d484ff..8a132eea470 100644 --- a/feature/isis/otg_tests/isis_drain_test/isis_drain_test.go +++ b/feature/isis/otg_tests/isis_drain_test/isis_drain_test.go @@ -33,18 +33,19 @@ import ( ) const ( - plen4 = 30 - plen6 = 126 - isisInstance = "DEFAULT" - areaAddress = "49.0001" - sysID = "1920.0000.2001" - v4Route = "203.0.113.0" - v4RoutePlen = 24 - v4IP = "203.0.113.1" - lag2MAC = "02:aa:bb:02:00:02" - lag3MAC = "02:aa:bb:03:00:02" - otgLAG2sysID = "640000000002" - otgLAG3sysID = "640000000003" + plen4 = 30 + plen6 = 126 + isisInstance = "DEFAULT" + areaAddress = "49.0001" + sysID = "1920.0000.2001" + v4Route = "203.0.113.0" + v4RoutePlen = 24 + v4IP = "203.0.113.1" + lag2MAC = "02:aa:bb:02:00:02" + lag3MAC = "02:aa:bb:03:00:02" + otgLAG2sysID = "640000000002" + otgLAG3sysID = "640000000003" + maxEcmpPaths uint8 = 16 ) var ( @@ -249,6 +250,7 @@ func configureISISDUT(t *testing.T, dut *ondatra.DUTDevice, intfs []string) { globalISIS.Instance = ygot.String(isisInstance) } globalISIS.LevelCapability = oc.Isis_LevelType_LEVEL_2 + globalISIS.SetMaxEcmpPaths(maxEcmpPaths) globalISIS.Net = []string{fmt.Sprintf("%v.%v.00", areaAddress, sysID)} 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) @@ -429,15 +431,17 @@ func configureTrafficFlows(t *testing.T, dut *ondatra.DUTDevice, otg *otg.OTG, f otg.PushConfig(t, top) t.Logf("Starting protocols and awaiting for ARP & IS-IS adjacencies") otg.StartProtocols(t) + time.Sleep(30 * time.Second) otgutils.WaitForARP(t, otg, top, "IPv4") + otgutils.WaitForARP(t, otg, top, "IPv6") awaitAdjacency(t, dut, agg2ID) awaitAdjacency(t, dut, agg3ID) - time.Sleep(15 * time.Second) } -func validateTrafficFlows(t *testing.T, dut *ondatra.DUTDevice, otg *otg.OTG, good []gosnappi.Flow, bad []gosnappi.Flow) { +func validateTrafficFlows(t *testing.T, dut *ondatra.DUTDevice, otg *otg.OTG, good []gosnappi.Flow, bad []gosnappi.Flow, nhCount int) { configureTrafficFlows(t, dut, otg, append(good, bad...)) + aftCheck(t, dut, nhCount) top := otg.GetConfig(t) otg.StartTraffic(t) @@ -475,7 +479,7 @@ func awaitAdjacency(t *testing.T, dut *ondatra.DUTDevice, intfName string) { 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, 2*time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { v, ok := val.Val() return v == oc.Isis_IsisInterfaceAdjState_UP && ok }).Await(t) @@ -485,6 +489,26 @@ func awaitAdjacency(t *testing.T, dut *ondatra.DUTDevice, intfName string) { } } +func aftCheck(t *testing.T, dut *ondatra.DUTDevice, nhCount int) { + aftsPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Afts() + + _, ok := gnmi.Watch(t, dut, aftsPath.Ipv4Entry(v4Route+"/24").State(), time.Minute, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + ipv4Entry, present := val.Val() + if !present { + return false + } + hopGroup := gnmi.Get(t, dut, aftsPath.NextHopGroup(ipv4Entry.GetNextHopGroup()).State()) + got := len(hopGroup.NextHop) + want := nhCount + t.Logf("Aft check for %s: Got %d nexthop,want %d", ipv4Entry.GetPrefix(), got, want) + return got == want + + }).Await(t) + + if !ok { + t.Errorf("Aft check failed for %s", v4Route+"/24") + } +} func TestDrain(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") @@ -498,15 +522,15 @@ func TestDrain(t *testing.T) { lag3Flow := createFlow(t, ateTopo, "trunk3-flow", atePort3.Name+".IPv4") t.Logf("Validating baseline traffic flow") - validateTrafficFlows(t, dut, otg, []gosnappi.Flow{ecmpFlows}, nil) + validateTrafficFlows(t, dut, otg, []gosnappi.Flow{ecmpFlows}, nil, 2) // Change trunk-2 metric to 1000 and validate the traffic flows changeMetric(t, dut, agg2ID, 1000) t.Logf("Validating traffic flows after increasing the metric") - validateTrafficFlows(t, dut, otg, []gosnappi.Flow{lag3Flow}, []gosnappi.Flow{lag2Flow}) + validateTrafficFlows(t, dut, otg, []gosnappi.Flow{lag3Flow}, []gosnappi.Flow{lag2Flow}, 1) // Restore trunk-2 metric changeMetric(t, dut, agg2ID, 10) t.Logf("Validating traffic flows after restoring the metric") - validateTrafficFlows(t, dut, otg, []gosnappi.Flow{ecmpFlows}, nil) + validateTrafficFlows(t, dut, otg, []gosnappi.Flow{ecmpFlows}, nil, 2) } diff --git a/feature/isis/otg_tests/isis_drain_test/metadata.textproto b/feature/isis/otg_tests/isis_drain_test/metadata.textproto index 65792b5364b..1bf76e3dbb6 100644 --- a/feature/isis/otg_tests/isis_drain_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_drain_test/metadata.textproto @@ -13,6 +13,7 @@ platform_exceptions: { missing_isis_interface_afi_safi_enable: true explicit_interface_in_default_vrf: true interface_enabled: true + isis_mpls_unsupported: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md b/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md index 23fe1fd5e7b..bbd69d07c78 100644 --- a/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md +++ b/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md @@ -1,4 +1,4 @@ -# RT-2.15 IS-IS Extensions for Segment Routing +# RT-2.15: IS-IS Extensions for Segment Routing ## Summary @@ -51,14 +51,6 @@ * Verify correct forwarding using Prefix SIDs. * Verify that corresponding SID forwarding counters are incremented. -### SR-1.2.4: Anycast SID Validation. - -* Configure the DUT to advertise an Anycast SID representing a service reachable via both loopback interfaces. -* Verify that the DUT advertises the Anycast SID. -* Send traffic from both ATEs towards the Anycast SID. -* Verify that traffic is load-balanced between the DUT's loopback interfaces based on IGP metrics. -* Verify that corresponding SID forwarding counters are incremented. - ## OpenConfig Path and RPC Coverage ```yaml @@ -93,4 +85,4 @@ rpcs: ``` ## Minimum DUT platform requirement -* FFF - fixed form factor \ No newline at end of file +* FFF - fixed form factor diff --git a/feature/isis/otg_tests/isis_extensions_segment_routing_test/isis_extensions_segment_routing_test.go b/feature/isis/otg_tests/isis_extensions_segment_routing_test/isis_extensions_segment_routing_test.go new file mode 100644 index 00000000000..f864d5494a6 --- /dev/null +++ b/feature/isis/otg_tests/isis_extensions_segment_routing_test/isis_extensions_segment_routing_test.go @@ -0,0 +1,795 @@ +// Copyright 2025 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. + +// +// RT-2.15 IS-IS Extensions for Segment Routing +// ============================================ +// +// Readme Location: +// +// https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/isis_extensions_segment_routing_test/README.md +// +// Topology: +// ========= +// | | +// [ ATE Port 1 ] ---- | DUT | ---- [ ATE Port 2] +// | | + +package isis_extensions_segment_routing_test + +import ( + "context" + "fmt" + "net" + "os" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/gnmi/oc/ocpath" + otgtelemetry "github.com/openconfig/ondatra/gnmi/otg" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + PTISIS = oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS + DUTAreaAddress = "49.0001" + ATEAreaAddress1 = "49.0002" + ATEAreaAddress2 = "49.0003" + DUTSysID = "1920.0000.2001" + ATE1SysID = "640000000001" + ATE2SysID = "640000000002" + ISISName = "DEFAULT" + staticMplsLabel = "16 16000" + srgbMplsLabelBlockName = "17000 20000" + srlbMplsLabelBlockName = "40000 41000" + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + srgbGlobalLowerBound = 17000 + srgbGlobalUpperBound = 20000 + v4Route = "203.0.113.1" + v6Route = "2001:db8::203:0:113:1" + v4NetName = "p2.d1.IsisIpv4.rr" + v6NetName = "p2.d1.IsisIpv6.rr" + v4FlowNameNodeSID = "nodeSidFlowv4" + v6FlowNameNodeSID = "nodeSidFlowv6" + v4FlowNamePrefixSID = "prefixSidFlowv4" + v6FlowNamePrefixSID = "prefixSidFlowv6" + nodeSIDLabelv4 = 17100 + nodeSIDLabelv6 = 17200 + nodeSidIndexv4 = 500 + nodeSidIndexv6 = 600 + nodeSIDLabelv4_1 = 17500 + nodeSIDLabelv6_1 = 17600 + prefixSIdIndexv4 = 300 + prefixSIdIndexv6 = 400 + prefixSIdLabelv4 = 17300 + prefixSIdLabelv6 = 17400 + packetPerSecond = 100 + packetSize = 512 + srgbId = "99.99.99.99" + srlbId = "88.88.88.88" +) + +var ( + atePort1 = attrs.Attributes{ + Name: "ateP1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort2 = attrs.Attributes{ + Name: "ateP2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE source", + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE destination-2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutLoopback1 = attrs.Attributes{ + Desc: "Loopback-ip1", + IPv4: "180.0.0.1", + IPv6: "180:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + + dutLoopback2 = attrs.Attributes{ + Desc: "Loopback-ip2", + IPv4: "179.0.0.1", + IPv6: "179:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + + dutLoopback3 = attrs.Attributes{ + Desc: "Loopback-ip3", + IPv4: "178.0.0.1", + IPv6: "178:db8::203:0:113:1", + IPv4Len: 32, + IPv6Len: 128, + } + + lb string +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestISISSegmentRouting(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + // configure DUT + configureDUT(t, dut) + if deviations.IsisSrgbSrlbUnsupported(dut) { + configureGlobalMPLS(t, dut) + } + configureDUTLoopback(t, dut, 0, dutLoopback1, true) + configureDUTLoopback(t, dut, 1, dutLoopback2, false) + configureDUTLoopback(t, dut, 2, dutLoopback3, false) + configurePrefixSID(t, dut) + + // configure ATE + topo := configureATE(t) + ate.OTG().PushConfig(t, topo) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), topo, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), topo, "IPv6") + + verifyISIS(t, dut, dut.Port(t, "port1").Name(), "dev1Isis") + verifyISIS(t, dut, dut.Port(t, "port2").Name(), "dev2Isis") + + t.Logf("Starting capture") + startCapture(t, ate) + t.Logf("Starting traffic") + ate.OTG().StartTraffic(t) + time.Sleep(time.Second * 15) + t.Logf("Stop traffic") + ate.OTG().StopTraffic(t) + t.Logf("Stop Capture") + stopCapture(t, ate) + + t.Run("Node SID Validation.", func(t *testing.T) { + verifyPrefixSids(t, ate, dutLoopback1.IPv4, uint32(nodeSIDLabelv4)) + otgutils.LogFlowMetrics(t, ate.OTG(), topo) + otgutils.LogPortMetrics(t, ate.OTG(), topo) + verifyTrafficNodeSID(t, ate) + t.Logf("Verify packet capture") + processCapture(t, ate.OTG(), "port2") + VerifyISISSRSIDCounters(t, dut, nodeSIDLabelv4) + }) + + t.Run("Prefix SID Validation", func(t *testing.T) { + verifyPrefixSids(t, ate, dutLoopback2.IPv4, uint32(prefixSIdLabelv4)) + VerifyISISSRSIDCounters(t, dut, prefixSIdLabelv4) + }) +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := gnmi.OC() + // Configure interfaces + p1 := dut.Port(t, "port1").Name() + i1 := dutPort1.NewOCInterface(p1, dut) + gnmi.Replace(t, dut, d.Interface(p1).Config(), i1) + + p2 := dut.Port(t, "port2").Name() + i2 := dutPort2.NewOCInterface(p2, dut) + gnmi.Replace(t, dut, d.Interface(p2).Config(), i2) + + dutConfPath := d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, ISISName) + dutConf := addISISOC(DUTAreaAddress, DUTSysID, p1, p2, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + +} + +// addISISOC configures basic IS-IS on a device. +func addISISOC(areaAddress, sysID, ifaceName1 string, ifaceName2 string, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + dev := &oc.Root{} + inst := dev.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := inst.GetOrCreateProtocol(PTISIS, ISISName) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + glob := isis.GetOrCreateGlobal() + if deviations.ISISInstanceEnabledRequired(dut) { + glob.Instance = ygot.String(ISISName) + } + glob.Net = []string{fmt.Sprintf("%v.%v.00", areaAddress, sysID)} + glob.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + glob.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + level := isis.GetOrCreateLevel(2) + level.MetricStyle = oc.Isis_MetricStyle_WIDE_METRIC + // Configure ISIS enabled flag at level + if deviations.ISISLevelEnabled(dut) { + level.Enabled = ygot.Bool(true) + } + intf := isis.GetOrCreateInterface(ifaceName1) + intf.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + intf.Enabled = ygot.Bool(true) + // Configure ISIS level at global mode if true else at interface mode + if deviations.ISISInterfaceLevel1DisableRequired(dut) { + intf.GetOrCreateLevel(1).Enabled = ygot.Bool(false) + } else { + intf.GetOrCreateLevel(2).Enabled = ygot.Bool(true) + } + glob.LevelCapability = oc.Isis_LevelType_LEVEL_2 + // Configure ISIS enable flag at interface level + intf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + intf.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + intf.Af = nil + } + + intf2 := isis.GetOrCreateInterface(ifaceName2) + intf2.CircuitType = oc.Isis_CircuitType_POINT_TO_POINT + intf2.Enabled = ygot.Bool(true) + // Configure ISIS level at global mode if true else at interface mode + if deviations.ISISInterfaceLevel1DisableRequired(dut) { + intf2.GetOrCreateLevel(1).Enabled = ygot.Bool(false) + } else { + intf2.GetOrCreateLevel(2).Enabled = ygot.Bool(true) + } + glob.LevelCapability = oc.Isis_LevelType_LEVEL_1_2 + // Configure ISIS enable flag at interface level + intf2.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV4, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + intf2.GetOrCreateAf(oc.IsisTypes_AFI_TYPE_IPV6, oc.IsisTypes_SAFI_TYPE_UNICAST).Enabled = ygot.Bool(true) + if deviations.ISISInterfaceAfiUnsupported(dut) { + intf2.Af = nil + } + + // ISIS Segment Routing configurations + isissr := prot.GetOrCreateIsis().GetOrCreateGlobal().GetOrCreateSegmentRouting() + isissr.Enabled = ygot.Bool(true) + isissr.Srgb = ygot.String(srgbMplsLabelBlockName) + isissr.Srlb = ygot.String(srlbMplsLabelBlockName) + + // SRGB and SRLB Configurations + segmentrouting := inst.GetOrCreateSegmentRouting() + srgb := segmentrouting.GetOrCreateSrgb(srgbId) + srgb.LocalId = ygot.String(srgbId) + srgb.SetMplsLabelBlocks([]string{srgbMplsLabelBlockName}) + + srlb := segmentrouting.GetOrCreateSrlb(srlbId) + srlb.LocalId = ygot.String(srlbId) + srlb.SetMplsLabelBlock(srlbMplsLabelBlockName) + + return prot +} + +func configurePrefixSID(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.IsisSrPrefixSegmentConfigUnsupported(dut) { + gnmiClient := dut.RawAPIs().GNMI(t) + + jsonConfig := fmt.Sprintf(` + router isis %s + segment-routing mpls + prefix-segment %s/%v index %d + prefix-segment %s/%v index %d + `, ISISName, dutLoopback2.IPv4, dutLoopback2.IPv4Len, prefixSIdIndexv4, dutLoopback2.IPv6, dutLoopback2.IPv6Len, prefixSIdIndexv6) + gpbSetRequest := buildCliConfigRequest(jsonConfig) + + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } + } + +} + +// Enable MPLS Forwarding +func configureGlobalMPLS(t *testing.T, dut *ondatra.DUTDevice) { + gnmiClient := dut.RawAPIs().GNMI(t) + + jsonConfig := fmt.Sprintf(` + mpls ip + mpls label range static %s + mpls label range isis-sr %s + mpls label range srlb %s + `, staticMplsLabel, srgbMplsLabelBlockName, srlbMplsLabelBlockName) + gpbSetRequest := buildCliConfigRequest(jsonConfig) + + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } +} + +// Support method to execute GNMIC commands +func buildCliConfigRequest(config string) *gpb.SetRequest { + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Origin: "cli", + Elem: []*gpb.PathElem{}, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_AsciiVal{ + AsciiVal: config, + }, + }, + }, + }, + } + return gpbSetRequest +} + +// configureATE sets up the ATE interfaces and BGP configurations. +func configureATE(t *testing.T) gosnappi.Config { + topo := gosnappi.NewConfig() + t.Log("Configure ATE interface") + port1 := topo.Ports().Add().SetName("port1") + port2 := topo.Ports().Add().SetName("port2") + + c1 := topo.Captures().Add().SetName("Capture") + c1.SetPortNames([]string{"port2"}) + + port1Dev := topo.Devices().Add().SetName(atePort1.Name + ".dev") + port1Eth := port1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + port1Eth.Connection().SetPortName(port1.Name()) + port1Ipv4 := port1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + port1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + port1Ipv6 := port1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + port1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + port2Dev := topo.Devices().Add().SetName(atePort2.Name + ".dev") + port2Eth := port2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + port2Eth.Connection().SetPortName(port2.Name()) + port2Ipv4 := port2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + port2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + port2Ipv6 := port2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + port2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + // Add IS-IS in ATE port1 + port1isis := port1Dev.Isis().SetSystemId(ATE1SysID).SetName("dev1Isis") + + // port 1 device 1 isis basic + port1isis.Basic().SetIpv4TeRouterId(port1Ipv4.Address()) + port1isis.Basic().SetHostname(port1isis.Name()) + port1isis.Basic().SetEnableWideMetric(true) + port1isis.Basic().SetLearnedLspFilter(true) + + // configure Segment Routing in ATEport1 + + sr := port1isis.SegmentRouting() + d1rtrCap1 := sr.RouterCapability() + d1rtrCap1.SetCustomRouterCapId(atePort1.IPv4) + d1rtrCap1.SetAlgorithms([]uint32{0}) + d1rtrCap1.SetSBit(gosnappi.IsisRouterCapabilitySBit.FLOOD) + d1rtrCap1.SetDBit(gosnappi.IsisRouterCapabilityDBit.DOWN) + srCap := d1rtrCap1.SrCapability() + srCap.Flags().SetIpv4Mpls(true).SetIpv6Mpls(true) + srCap.SrgbRanges().Add().SetStartingSid(uint32(srgbGlobalLowerBound)).SetRange(uint32(srgbGlobalUpperBound)) + + devIsisport1 := port1isis.Interfaces().Add().SetEthName(port1Dev.Ethernets().Items()[0].Name()). + SetName("devIsisPort1").SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_1_2).SetMetric(10) + + devIsisport1.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + // Add IS-IS in ATE port2 + port2isis := port2Dev.Isis().SetSystemId(ATE2SysID).SetName("dev2Isis") + + port2isis.Basic().SetIpv4TeRouterId(port2Ipv4.Address()) + port2isis.Basic().SetHostname(port2isis.Name()) + port2isis.Basic().SetEnableWideMetric(true) + port2isis.Basic().SetLearnedLspFilter(true) + + // configure Segment Routing in ATEport2 + + sr1 := port2isis.SegmentRouting() + d2rtrCap1 := sr1.RouterCapability() + d2rtrCap1.SetCustomRouterCapId(atePort2.IPv4) + d2rtrCap1.SetAlgorithms([]uint32{0}) + d2rtrCap1.SetSBit(gosnappi.IsisRouterCapabilitySBit.FLOOD) + d2rtrCap1.SetDBit(gosnappi.IsisRouterCapabilityDBit.DOWN) + srCap1 := d2rtrCap1.SrCapability() + srCap1.Flags().SetIpv4Mpls(true).SetIpv6Mpls(true) + srCap1.SrgbRanges().Add().SetStartingSid(uint32(srgbGlobalLowerBound)).SetRange(uint32(srgbGlobalUpperBound)) + + devIsisport2 := port2isis.Interfaces().Add().SetEthName(port2Dev.Ethernets().Items()[0].Name()). + SetName("devIsisPort2").SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT). + SetLevelType(gosnappi.IsisInterfaceLevelType.LEVEL_1_2).SetMetric(10) + + devIsisport2.Advanced().SetAutoAdjustMtu(true).SetAutoAdjustArea(true).SetAutoAdjustSupportedProtocols(true) + + // port 1 device 1 isis v4 routes + p2d1Isisv4routes := port2isis.V4Routes().Add().SetName(v4NetName).SetLinkMetric(10). + SetOriginType(gosnappi.IsisV4RouteRangeOriginType.INTERNAL) + p2d1Isisv4routes.Addresses().Add().SetAddress(v4Route).SetPrefix(32).SetCount(1).SetStep(1) + + p2d1Isisv4routes.SetPrefixAttrEnabled(true).SetRFlag(true).SetNFlag(true) + p2d1Isisv4routes.PrefixSids().Add().SetSidIndices([]uint32{nodeSidIndexv4}).SetRFlag(false).SetNFlag(true).SetPFlag(false).SetAlgorithm(0) + + p2d1Isisv6routes := port2isis.V6Routes().Add().SetName(v6NetName).SetLinkMetric(10). + SetOriginType(gosnappi.IsisV6RouteRangeOriginType.INTERNAL) + p2d1Isisv6routes.Addresses().Add().SetAddress(v6Route).SetPrefix(128).SetCount(1).SetStep(1) + + p2d1Isisv6routes.SetPrefixAttrEnabled(true).SetRFlag(true).SetNFlag(true) + p2d1Isisv6routes.PrefixSids().Add().SetSidIndices([]uint32{nodeSidIndexv6}).SetRFlag(false).SetNFlag(true).SetPFlag(false).SetAlgorithm(0) + + // We generate traffic entering along port1 and destined for port2 + srcIpv4 := port1Dev.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + srcIpv6 := port1Dev.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + dstIPv4 := port2Dev.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + dstIPv6 := port2Dev.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + // configure v4, v6 traffic sending via node SID + t.Log("Configuring v4 traffic flow sending via node SID") + v4Flow := topo.Flows().Add().SetName(v4FlowNameNodeSID) + v4Flow.Metrics().SetEnable(true) + v4Flow.TxRx().Device(). + SetTxNames([]string{srcIpv4.Name()}). + SetRxNames([]string{v4NetName}) + v4Flow.Size().SetFixed(packetSize) + v4Flow.Rate().SetPps(packetPerSecond) + v4Flow.Duration().Continuous() + e1 := v4Flow.Packet().Add().Ethernet() + e1.Src().SetValue(atePort1.MAC) + e1.Dst().Auto() + mpls := v4Flow.Packet().Add().Mpls() + mpls.Label().SetValue(nodeSIDLabelv4_1) + + v4 := v4Flow.Packet().Add().Ipv4() + v4.Src().SetValue(atePort1.IPv4) + v4.Dst().SetValue(v4Route) + + //egress tracking + v4Flow.EgressPacket().Add().Ethernet() + mplsTcTracking := v4Flow.EgressPacket().Add().Mpls() + tr1 := mplsTcTracking.Label().MetricTags().Add() + tr1.SetName("popped-MplsLabel1") + tr1.SetOffset(17) + tr1.SetLength(3) + + t.Log("Configuring v6 traffic flow sending via node SID") + v6Flow := topo.Flows().Add().SetName(v6FlowNameNodeSID) + v6Flow.Metrics().SetEnable(true) + v6Flow.TxRx().Device(). + SetTxNames([]string{srcIpv6.Name()}). + SetRxNames([]string{v6NetName}) + v6Flow.Size().SetFixed(packetSize) + v6Flow.Rate().SetPps(packetPerSecond) + v6Flow.Duration().Continuous() + e2 := v6Flow.Packet().Add().Ethernet() + e2.Src().SetValue(atePort1.MAC) + e2.Dst().Auto() + mplsv6 := v6Flow.Packet().Add().Mpls() + mplsv6.Label().SetValue(nodeSIDLabelv6_1) + mplsv6.BottomOfStack().SetValue(0) + v6 := v6Flow.Packet().Add().Ipv6() + v6.Src().SetValue(atePort1.IPv6) + v6.Dst().SetValue(v6Route) + + //Egress tracking + v6Flow.EgressPacket().Add().Ethernet() + mplsTcTracking1 := v6Flow.EgressPacket().Add().Mpls() + tr2 := mplsTcTracking1.Label().MetricTags().Add() + tr2.SetName("Popped-MplsLabel1v6") + tr2.SetOffset(17) + tr2.SetLength(3) + + // configure v4, v6 traffic pointing to prefix SID + t.Log("Configuring v4 traffic flow pointing to Prefix SID") + v4Flow1 := topo.Flows().Add().SetName(v4FlowNamePrefixSID) + v4Flow1.Metrics().SetEnable(true) + v4Flow1.TxRx().Device(). + SetTxNames([]string{srcIpv4.Name()}). + SetRxNames([]string{dstIPv4.Name()}) + v4Flow1.Size().SetFixed(packetSize) + v4Flow1.Rate().SetPps(packetPerSecond) + v4Flow1.Duration().Continuous() + e3 := v4Flow1.Packet().Add().Ethernet() + e3.Src().SetValue(atePort1.MAC) + e3.Dst().Auto() + mpls3 := v4Flow1.Packet().Add().Mpls() + mpls3.Label().SetValue(prefixSIdLabelv4) + + v41 := v4Flow1.Packet().Add().Ipv4() + v41.Src().SetValue(atePort1.IPv4) + v41.Dst().SetValue(dutLoopback2.IPv4) + + //egress tracking + v4Flow1.EgressPacket().Add().Ethernet() + mplsTcTracking3 := v4Flow1.EgressPacket().Add().Mpls() + tr3 := mplsTcTracking3.Label().MetricTags().Add() + tr3.SetName("prefix-sidv4-check") + tr3.SetOffset(17) + tr3.SetLength(3) + + t.Log("Configuring v6 traffic flow pointing to Prefix SID") + v6Flow1 := topo.Flows().Add().SetName(v6FlowNamePrefixSID) + v6Flow1.Metrics().SetEnable(true) + v6Flow1.TxRx().Device(). + SetTxNames([]string{srcIpv6.Name()}). + SetRxNames([]string{dstIPv6.Name()}) + v6Flow1.Size().SetFixed(packetSize) + v6Flow1.Rate().SetPps(packetPerSecond) + v6Flow1.Duration().Continuous() + e4 := v6Flow1.Packet().Add().Ethernet() + e4.Src().SetValue(atePort1.MAC) + e4.Dst().Auto() + mplsv61 := v6Flow1.Packet().Add().Mpls() + mplsv61.Label().SetValue(prefixSIdLabelv6) + mplsv61.BottomOfStack().SetValue(0) + v61 := v6Flow1.Packet().Add().Ipv6() + v61.Src().SetValue(atePort1.IPv6) + v61.Dst().SetValue(dutLoopback2.IPv6) + + //Egress tracking + v6Flow1.EgressPacket().Add().Ethernet() + mplsTcTracking4 := v6Flow1.EgressPacket().Add().Mpls() + tr4 := mplsTcTracking4.Label().MetricTags().Add() + tr4.SetName("prefixv6-label") + tr4.SetOffset(17) + tr4.SetLength(3) + + return topo +} + +func configureDUTLoopback(t *testing.T, dut *ondatra.DUTDevice, id int, dutLoopback attrs.Attributes, enablenodeSID bool) { + dutConfIsisPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, ISISName) + + dev := &oc.Root{} + inst := dev.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + prot := inst.GetOrCreateProtocol(PTISIS, ISISName) + prot.Enabled = ygot.Bool(true) + isis := prot.GetOrCreateIsis() + t.Helper() + lb = netutil.LoopbackInterface(t, dut, id) + lo0 := gnmi.OC().Interface(lb).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, lo0.Ipv4().AddressAny().State()) + ipv6Addrs := gnmi.LookupAll(t, dut, lo0.Ipv6().AddressAny().State()) + foundV4 := false + for _, ip := range ipv4Addrs { + if v, ok := ip.Val(); ok { + foundV4 = true + dutLoopback.IPv4 = v.GetIp() + break + } + } + foundV6 := false + for _, ip := range ipv6Addrs { + if v, ok := ip.Val(); ok { + foundV6 = true + dutLoopback.IPv6 = v.GetIp() + break + } + } + if !foundV4 || !foundV6 { + lo1 := dutLoopback.NewOCInterface(lb, dut) + lo1.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, gnmi.OC().Interface(lb).Config(), lo1) + isisIntf := isis.GetOrCreateInterface(lo1.GetName()) + isisIntf.GetOrCreateInterfaceRef().Interface = ygot.String(lo1.GetName()) + isisIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + isisIntf.InterfaceRef = nil + } + isisIntf.Enabled = ygot.Bool(true) + gnmi.Update(t, dut, dutConfIsisPath.Config(), prot) + + // enable node segment + if enablenodeSID { + if deviations.IsisSrNodeSegmentConfigUnsupported(dut) { + gnmiClient := dut.RawAPIs().GNMI(t) + jsonConfig := fmt.Sprintf(` + interface %s + node-segment ipv4 label %v + node-segment ipv6 label %v + `, lo1.GetName(), nodeSIDLabelv4, nodeSIDLabelv6) + + gpbSetRequest := buildCliConfigRequest(jsonConfig) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } + } + } + + } + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, lb, deviations.DefaultNetworkInstance(dut), 0) + } + +} + +func verifyTrafficNodeSID(t *testing.T, ate *ondatra.ATEDevice) { + recvMetricV4 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v4FlowNameNodeSID).State()) + recvMetricV6 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v6FlowNameNodeSID).State()) + + framesTxV4 := recvMetricV4.GetCounters().GetOutPkts() + framesRxV4 := recvMetricV4.GetCounters().GetInPkts() + framesTxV6 := recvMetricV6.GetCounters().GetOutPkts() + framesRxV6 := recvMetricV6.GetCounters().GetInPkts() + + t.Logf("Starting V4 traffic validation") + if framesTxV4 == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if framesRxV4 == framesTxV4 { + t.Logf("Traffic validation successful for [%s] FramesTx: %d FramesRx: %d", v4FlowNameNodeSID, framesTxV4, framesRxV4) + } else { + t.Errorf("Traffic validation failed for [%s] FramesTx: %d FramesRx: %d", v4FlowNameNodeSID, framesTxV4, framesRxV4) + } + t.Logf("Starting V6 traffic validation") + if framesTxV6 == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if framesRxV6 == framesTxV6 { + t.Logf("Traffic validation successful for [%s] FramesTx: %d FramesRx: %d", v6FlowNameNodeSID, framesTxV6, framesRxV6) + } else { + t.Errorf("Traffic validation failed for [%s] FramesTx: %d FramesRx: %d", v6FlowNameNodeSID, framesTxV6, framesRxV6) + } +} + +// startCapture starts the capture on the otg ports +func startCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) +} + +// stopCapture starts the capture on the otg ports +func stopCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, cs) +} + +func processCapture(t *testing.T, otg *otg.OTG, port string) { + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(port)) + time.Sleep(30 * time.Second) + pcapFile, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := pcapFile.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + pcapFile.Close() + validatePackets(t, pcapFile.Name()) +} + +func validatePackets(t *testing.T, filename string) { + packetCount := int32(0) + mplsPacketCount := int32(0) + + handle, err := pcap.OpenOffline(filename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + + for packet := range packetSource.Packets() { + if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv4) + if ip.SrcIP.Equal(net.ParseIP(atePort1.IPv4)) { + if mplsLayer := packet.Layer(layers.LayerTypeMPLS); mplsLayer != nil { + mpls, _ := mplsLayer.(*layers.MPLS) + if mpls.Label == nodeSIDLabelv4 { + mplsPacketCount += 1 + } + + } + } + packetCount += 1 + } + } + if mplsPacketCount != 0 { + t.Errorf("NodeSID is not popped up by the DUT") + } +} + +func verifyISIS(t *testing.T, dut *ondatra.DUTDevice, intfName string, atesysId string) { + if ok := awaitAdjacency(t, dut, intfName, []oc.E_Isis_IsisInterfaceAdjState{oc.Isis_IsisInterfaceAdjState_UP}); !ok { + t.Fatal("ISIS Adjacency is Down for interface") + } +} + +// awaitAdjacency wait for adjacency to be up/down +func awaitAdjacency(t *testing.T, dut *ondatra.DUTDevice, intfName string, state []oc.E_Isis_IsisInterfaceAdjState) bool { + isisPath := ocpath.Root().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, ISISName).Isis() + intf := isisPath.Interface(intfName) + query := intf.LevelAny().AdjacencyAny().AdjacencyState().State() + _, ok := gnmi.WatchAll(t, dut, query, 15*time.Second, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { + v, ok := val.Val() + for _, s := range state { + if (v == s) && ok { + return true + } + } + return false + }).Await(t) + return ok +} + +func verifyPrefixSids(t *testing.T, ate *ondatra.ATEDevice, ipaddr string, label uint32) { + + t.Run("Verify Prefix through TLV ", func(t *testing.T) { + _, ok := gnmi.WatchAll(t, ate.OTG(), gnmi.OTG().IsisRouter("dev1Isis").LinkStateDatabase().LspsAny().Tlvs().ExtendedIpv4Reachability().Prefix(ipaddr).State(), 30*time.Second, func(v *ygnmi.Value[*otgtelemetry.IsisRouter_LinkStateDatabase_Lsps_Tlvs_ExtendedIpv4Reachability_Prefix]) bool { + _, present := v.Val() + return present + }).Await(t) + + if ok { + t.Logf("Prefix found, want: %s", ipaddr) + } else { + t.Errorf("Prefix Not found. want: %s", ipaddr) + } + }) +} + +func VerifyISISSRSIDCounters(t *testing.T, dut *ondatra.DUTDevice, mplsLabel oc.UnionUint32) { + + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + _, ok := gnmi.WatchAll(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().SignalingProtocols().SegmentRouting().AggregateSidCounterAny().InPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("Unable to find input matched packets related to MPLS label") + } + + _, ok1 := gnmi.WatchAll(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().SignalingProtocols().SegmentRouting().AggregateSidCounterAny().OutPkts().State(), timeout, isPresent).Await(t) + if !ok1 { + t.Errorf("Unable to find output matched packets related to MPLS label") + } + + // MplsLabel:= mplsLabel oc.E_AggregateSidCounter_MplsLabel] + + inpkts := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().SignalingProtocols().SegmentRouting().AggregateSidCounter(mplsLabel).InPkts().State() + + inpcktstats := gnmi.Get(t, dut, inpkts) + t.Log(inpcktstats) + + OutPkts := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().SignalingProtocols().SegmentRouting().AggregateSidCounter(mplsLabel).OutPkts() + + outpcktstats := gnmi.Get(t, dut, OutPkts.State()) + t.Log(outpcktstats) + + if inpcktstats == 0 || outpcktstats == 0 { + t.Errorf("Unable to find output matched packets related to MPLS label") + } +} diff --git a/feature/isis/otg_tests/isis_extensions_segment_routing_test/metadata.textproto b/feature/isis/otg_tests/isis_extensions_segment_routing_test/metadata.textproto new file mode 100644 index 00000000000..0ac37052057 --- /dev/null +++ b/feature/isis/otg_tests/isis_extensions_segment_routing_test/metadata.textproto @@ -0,0 +1,25 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ff85241a-e7db-4f82-8b66-07320cdd7132" +plan_id: "RT-2.15" +description: "IS-IS Extensions for Segment Routing" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + isis_instance_enabled_required: true + missing_isis_interface_afi_safi_enable: true + interface_enabled: true + default_network_instance: "default" + isis_interface_afi_unsupported: true + isis_timers_csnp_interval_unsupported: true + isis_counter_manual_address_drop_from_areas_unsupported: true + isis_counter_part_changes_unsupported: true + isis_metric_style_telemetry_unsupported: true + isis_srgb_srlb_unsupported: true + isis_sr_node_segment_config_unsupported: true + } +} \ No newline at end of file diff --git a/feature/isis/otg_tests/isis_interface_level_passive_test/isis_interface_level_passive_test.go b/feature/isis/otg_tests/isis_interface_level_passive_test/isis_interface_level_passive_test.go index eb0dde965b0..90da97a9ec5 100644 --- a/feature/isis/otg_tests/isis_interface_level_passive_test/isis_interface_level_passive_test.go +++ b/feature/isis/otg_tests/isis_interface_level_passive_test/isis_interface_level_passive_test.go @@ -196,10 +196,12 @@ func TestISISLevelPassive(t *testing.T) { switch dut.Vendor() { case ondatra.CISCO: isispassiveconfig = fmt.Sprintf("router isis DEFAULT\n interface %s\n passive\n", intfName) + helpers.GnmiCLIConfig(t, dut, isispassiveconfig) + case ondatra.ARISTA: + gnmi.Update(t, ts.DUT, statePath.Interface(intfName).Passive().Config(), true) default: t.Fatalf("Unsupported vendor %s for deviation 'IsisInterfaceLevelPassiveUnsupported'", dut.Vendor()) } - helpers.GnmiCLIConfig(t, dut, isispassiveconfig) } else { gnmi.Update(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().Config(), true) } @@ -221,10 +223,12 @@ func TestISISLevelPassive(t *testing.T) { switch dut.Vendor() { case ondatra.CISCO: isispassiveconfig = fmt.Sprintf("router isis DEFAULT\n interface %s\n no passive\n", intfName) + helpers.GnmiCLIConfig(t, dut, isispassiveconfig) + case ondatra.ARISTA: + gnmi.Update(t, ts.DUT, statePath.Interface(intfName).Passive().Config(), false) default: t.Fatalf("Unsupported vendor %s for deviation 'IsisInterfaceLevelPassiveUnsupported'", dut.Vendor()) } - helpers.GnmiCLIConfig(t, dut, isispassiveconfig) } else { gnmi.Update(t, ts.DUT, statePath.Interface(intfName).Level(2).Passive().Config(), false) } diff --git a/feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto b/feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto index b8ef43a9b41..51e4ff79b19 100644 --- a/feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto +++ b/feature/isis/otg_tests/isis_interface_level_passive_test/metadata.textproto @@ -43,6 +43,7 @@ platform_exceptions: { isis_timers_csnp_interval_unsupported: true isis_counter_manual_address_drop_from_areas_unsupported: true isis_counter_part_changes_unsupported: true + isis_interface_level_passive_unsupported: true } } platform_exceptions: { diff --git a/feature/isis/otg_tests/isis_scale_test/README.MD b/feature/isis/otg_tests/isis_scale_test/README.MD new file mode 100644 index 00000000000..36821ad370a --- /dev/null +++ b/feature/isis/otg_tests/isis_scale_test/README.MD @@ -0,0 +1,554 @@ +# RT-2.14: IS-IS scale test + +## Summary + +This test suite should ensure that the router will be able to handle below IS-IS +scale: + +* 3200 IS Node +* 3200 x /32 IPv4 prefix +* 3200 x /25 IPv4 prefix +* 4600 x /64 IPv6 prefix +* 53600 x ISIS link + +Every test in this test suite should be able to test the router perfomance under +different circumstances e.g static static (with no route flaps) dynamic state +(with route flaps).. etc. + + +## RT-2.14.1: IS-IS scale test with static routes (no churn and no new LSPs) + +### Testbed type + +[TESTBED_DUT_ATE_2LINKS](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +### Topology + +```mermaid +graph LR + Block1[ISIS Block1 20x20] <--> R1[ATE router R1] + Block3[ISIS Block3 20x20] <--> R1 + Block5[ISIS Block5 12x12] <--> R1 + Block7[ISIS Block7 12x12] <--> R1 + Block9[ISIS Block9 16x16] <--> R1 + Block11[ISIS Block11 16x16] <--> R1 + R1 <--> |IS-IS |DUT[DUT] + DUT <--> | IS-IS | R2[ATE router R2] + R2 <--> Block2[ISIS Block2 20x20] + R2 <--> Block4[ISIS Block4 20x20] + R2 <--> Block6[ISIS Block6 12x12] + R2 <--> Block8[ISIS Block8 12x12] + R2 <--> Block10[ISIS Block10 16x16] + R2 <--> Block12[ISIS Block12 16x16] +``` +### DUT and OTG properties + + +Device | Port1 IPv4 | Port1 IPv6 | Port2 IPv4| Port2 IPv4| ISIS AREA | ISIS ID +:------ | :-------------- | :------------- | :--------- | :-------- | :-------- | :--------: +DUT | 192.168.0.5/30 | 2001:db8::1/126 | 192.168.0.9/30 | 2002:db8::1/126 | 49001| 640000000002 +ATE R1 | 192.168.0.6/30| 2001:db8::2/126 |N/A|N/A| 49001| 640000000003 +ATE R2 | N/A|N/A| 192.168.0.10/30 | 2002:db8::2/126| 49001| 640000000004 + +All Simulated ISIS blocks on OTG will have a grid topology with the below specs + +Block Name | Dimension | Lo0 IPv4,IPv6 and ISIS sys ID first octet | Count of paralle links between 2 nodes | Link IPv4 range +:------ | :-------- | :-------- | :-------- | :--------: +Block1 | 20x20 | 20 | 2 | 21.0.0.0/8 +Block2 | 20x20 | 23 | 2 | 24.0.0.0/8 +Block3 | 20x20 | 26 | 2 | 27.0.0.0/8 +Block4 | 20x20 | 29 | 2 | 30.0.0.0/8 +Block5 | 12x12 | 32 | 20 | 33.0.0.0/8 +Block6 | 12x12 | 35 | 20 | 36.0.0.0/8 +Block7 | 12x12 | 38 | 20 | 39.0.0.0/8 +Block8 | 12x12 | 41 | 20 | 42.0.0.0/8 +Block9 | 16x16 | 44 | 20 | 45.0.0.0/8 +Block10 | 16x16 | 47 | 20 | 48.0.0.0/8 +Block11 | 16x16 | 50 | 20 | 51.0.0.0/8 +Block12 | 16x16 | 53 | 20 | 54.0.0.0/8 + + + + +### Procedure + +#### Initial Setup: +* Connect DUT port-1 and port-2 to OTG port-1 and port-2 respectively +* Configure OTG : + 1. Create an agg interface "Agg1" with LACP + 2. Create emulated router R1 with an ethernet interface + 3. Connect the ethernet to the created Agg interface + 4. Configure IPv4 and IPv6 on R1 ethernet + 5. Configure ISIS on R1 + * Set ISIS System ID + * Set ISIS area ID + * Disable "Hello Padding" + * Enable wide metrics + * Enable ISIS on the ethernet interface with Level 2 only and circuite type of point to point + * Enable ISIS 3 way handshaking + 6. Repeat steps 1 to 5 for Agg2 and R2 + 7. Create IP Flows from Agg1 to the below ranges + * 24.0.0.0 - 24.0.0.100 + * 30.0.0.0 - 30.0.0.100 + * 36.0.0.0 - 36.0.0.100 + * 42.0.0.0 - 42.0.0.100 + * 48.0.0.0 - 48.0.0.100 + * 54.0.0.0 - 54.0.0.100 + + 8. Create IP Flows from Agg2 to the below ranges + * 21.0.0.0 - 24.0.0.100 + * 27.0.0.0 - 30.0.0.100 + * 33.0.0.0 - 36.0.0.100 + * 39.0.0.0 - 42.0.0.100 + * 45.0.0.0 - 48.0.0.100 + * 51.0.0.0 - 51.0.0.100 + 9. Push configuration to OTG +* Start protocols on OTG +* Configure DUT: + 1. Configure the default network instance and push path to DUT + 2. Create an LAG interface set the interface IP address and LACP and push path to DUT + 3. Create a physical interface and attach it to the LAG . + 4. Repeat steps 1 to 3 for the second LAG . + 5. Configure ISIS for DUT : + * Create ISIS instance under the default routing instance + * Set ISIS net address (area ID + System ID) + * Set ISIS level capability to level 2 only + * Disable "Hello Padding" + * Set "LSP Refresh Timer" and "LSP Lifetime Timer" to 65218 and 65535 respectively. + * Set "SPF First Interval" and "SPF Hold Interval" to 200 and 2000 respectively. + * Set metric style to wide + * Enable IPv4-unicast and IPv6-unicast address families. + * Enable ISIS on the Loopback and the 2 LAG interfaces. + * Set Loopback to passive mode and the LAG interface circuit type to point to point. + * Push the configuration to the DUT. +* Wait for 1 min than confirm that DUT has 2 ISIS adjacencies up. + +#### Testing steps + +* Start traffic flows +* Wait for 2 mins +* Collect the below metrics form the DUT : + 1. Count of ISIS Nodes + 2. Count of LSPs + 3. Count of ISIS IPv4 routes + 4. Count of ISIS IPv6 routes +* Stop traffic flows +* Collect the number of RX and TX traffic flows packets + +#### Test pass fail criteria + +* If any of the below conditions are not meet test should be declared as a "FAIL" + 1. Count of ISIS adjacencies in the "UP" state is less than 2 + 2. Count of any of the below metrics was not equal to the below values : + a. ISIS nodes = 3200 + b. ISIS LSPs = 8000 + c. ISIS IPv4 routes = 9216 + d. ISIS IPv6 routes = 3200 + 3. Number of TX packets is not equal to RX packets. + +## Canonical OC + +```json + "openconfig-network-instance:network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "config": { + "identifier": "openconfig-policy-types:ISIS", + "name": "DEFAULT" + }, + "identifier": "openconfig-policy-types:ISIS", + "isis": { + "global": { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "enabled": true, + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "enabled": true, + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "hello-padding": "DISABLE", + "level-capability": "LEVEL_2", + "net": [ + "49.0001.1920.0000.2001.00" + ] + }, + "mpls": { + "igp-ldp-sync": { + "config": { + "enabled": false + } + } + }, + "timers": { + "config": { + "lsp-lifetime-interval": 65535, + "lsp-refresh-interval": 65218 + }, + "spf": { + "config": { + "spf-first-interval": "200", + "spf-hold-interval": "2000" + } + } + } + }, + "interfaces": { + "interface": [ + { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "enabled": true, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "enabled": true, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "circuit-type": "POINT_TO_POINT", + "enabled": true, + "interface-id": "Bundle-Ether1" + }, + "interface-id": "Bundle-Ether1", + "levels": { + "level": [ + { + "config": { + "enabled": false, + "level-number": 1 + }, + "level-number": 1 + }, + { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "enabled": true, + "level-number": 2 + }, + "level-number": 2, + "timers": { + "config": { + "hello-multiplier": 6 + } + } + } + ] + }, + "mpls": { + "igp-ldp-sync": { + "config": { + "enabled": false + } + } + }, + "timers": { + "config": { + "lsp-pacing-interval": "50" + } + } + }, + { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "enabled": true, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "enabled": true, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "circuit-type": "POINT_TO_POINT", + "enabled": true, + "interface-id": "Bundle-Ether2" + }, + "interface-id": "Bundle-Ether2", + "levels": { + "level": [ + { + "config": { + "enabled": false, + "level-number": 1 + }, + "level-number": 1 + }, + { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "enabled": true, + "level-number": 2 + }, + "level-number": 2, + "timers": { + "config": { + "hello-multiplier": 6 + } + } + } + ] + }, + "mpls": { + "igp-ldp-sync": { + "config": { + "enabled": false + } + } + }, + "timers": { + "config": { + "lsp-pacing-interval": "50" + } + } + }, + { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "enabled": true, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "enabled": true, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "circuit-type": "POINT_TO_POINT", + "enabled": true, + "interface-id": "Loopback0" + }, + "interface-id": "Loopback0", + "levels": { + "level": [ + { + "config": { + "enabled": false, + "level-number": 1 + }, + "level-number": 1 + }, + { + "afi-safi": { + "af": [ + { + "afi-name": "openconfig-isis-types:IPV4", + "config": { + "afi-name": "openconfig-isis-types:IPV4", + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + }, + { + "afi-name": "openconfig-isis-types:IPV6", + "config": { + "afi-name": "openconfig-isis-types:IPV6", + "metric": 10, + "safi-name": "openconfig-isis-types:UNICAST" + }, + "safi-name": "openconfig-isis-types:UNICAST" + } + ] + }, + "config": { + "enabled": true, + "level-number": 2 + }, + "level-number": 2, + "timers": { + "config": { + "hello-multiplier": 6 + } + } + } + ] + }, + "mpls": { + "igp-ldp-sync": { + "config": { + "enabled": false + } + } + }, + "timers": { + "config": { + "lsp-pacing-interval": "50" + } + } + } + ] + }, + "levels": { + "level": [ + { + "config": { + "level-number": 2, + "metric-style": "WIDE_METRIC" + }, + "level-number": 2 + } + ] + } + }, + "name": "DEFAULT" + } + ] + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage +```yaml +paths: + ## Config Parameter Coverage + + +/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/global/timers/spf/config/spf-hold-interval: +/network-instances/network-instance/protocols/protocol/isis/global/timers/spf/config/spf-first-interval: +/network-instances/network-instance/protocols/protocol/isis/global/timers/config/lsp-refresh-interval: +/network-instances/network-instance/protocols/protocol/isis/global/timers/config/lsp-lifetime-interval: +/network-instances/network-instance/protocols/protocol/isis/global/mpls/igp-ldp-sync/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/config/metric-style: +/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/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/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: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/timers/config/lsp-pacing-interval: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/mpls/igp-ldp-sync/config/enabled: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/metric: + + +## Telemetry Parameter Coverage + +/interfaces/interface/state/admin-status: +/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state: +/network-instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/state/lsp-id: +/network-instances/network-instance/afts/aft-summaries/ipv4-unicast/protocols/protocol/state/counters/aft-entries: +/network-instances/network-instance/afts/aft-summaries/ipv6-unicast/protocols/protocol/state/counters/aft-entries: +/instances/network-instance/protocols/protocol/isis/levels/level/link-state-database/lsp/tlvs/tlv: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +``` diff --git a/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto b/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto index 8d0a16dd7d4..fe50d02a661 100644 --- a/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto +++ b/feature/isis/otg_tests/static_route_isis_redistribution/metadata.textproto @@ -36,7 +36,6 @@ platform_exceptions: { } deviations: { isis_level_enabled: true - skip_setting_disable_metric_propagation: true routing_policy_tag_set_embedded: true } } diff --git a/feature/isis/otg_tests/static_route_isis_redistribution/static_route_isis_redistribution_test.go b/feature/isis/otg_tests/static_route_isis_redistribution/static_route_isis_redistribution_test.go index 24fa7ae73f0..ca9952299dd 100644 --- a/feature/isis/otg_tests/static_route_isis_redistribution/static_route_isis_redistribution_test.go +++ b/feature/isis/otg_tests/static_route_isis_redistribution/static_route_isis_redistribution_test.go @@ -170,7 +170,7 @@ func isisImportPolicyConfig(t *testing.T, dut *ondatra.DUTDevice, policyName str srcProto oc.E_PolicyTypes_INSTALL_PROTOCOL_TYPE, dstProto oc.E_PolicyTypes_INSTALL_PROTOCOL_TYPE, addfmly oc.E_Types_ADDRESS_FAMILY, - metricPropagation bool) { + metricPropagation bool, operation string) { t.Log("configure redistribution under isis") @@ -178,31 +178,35 @@ func isisImportPolicyConfig(t *testing.T, dut *ondatra.DUTDevice, policyName str batchSet := &gnmi.SetBatch{} d := oc.Root{} - tableConn := d.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(srcProto, dstProto, addfmly) - tableConn.SetImportPolicy([]string{policyName}) - if !deviations.SkipSettingDisableMetricPropagation(dut) { - tableConn.SetDisableMetricPropagation(metricPropagation) - } - if deviations.EnableTableConnections(dut) { - fptest.ConfigEnableTbNative(t, dut) - } - gnmi.BatchReplace(batchSet, gnmi.OC().NetworkInstance(dni).TableConnection(srcProto, dstProto, addfmly).Config(), tableConn) - - if deviations.SamePolicyAttachedToAllAfis(dut) { - if addfmly == oc.Types_ADDRESS_FAMILY_IPV4 { - addfmly = oc.Types_ADDRESS_FAMILY_IPV6 - } else { - addfmly = oc.Types_ADDRESS_FAMILY_IPV4 - } - tableConn1 := d.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(srcProto, dstProto, addfmly) - tableConn1.SetImportPolicy([]string{policyName}) + if operation == "set" { + tableConn := d.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(srcProto, dstProto, addfmly) + tableConn.SetImportPolicy([]string{policyName}) if !deviations.SkipSettingDisableMetricPropagation(dut) { - tableConn1.SetDisableMetricPropagation(metricPropagation) + tableConn.SetDisableMetricPropagation(metricPropagation) + } + if deviations.EnableTableConnections(dut) { + fptest.ConfigEnableTbNative(t, dut) + } + gnmi.BatchReplace(batchSet, gnmi.OC().NetworkInstance(dni).TableConnection(srcProto, dstProto, addfmly).Config(), tableConn) + + if deviations.SamePolicyAttachedToAllAfis(dut) { + if addfmly == oc.Types_ADDRESS_FAMILY_IPV4 { + addfmly = oc.Types_ADDRESS_FAMILY_IPV6 + } else { + addfmly = oc.Types_ADDRESS_FAMILY_IPV4 + } + tableConn1 := d.GetOrCreateNetworkInstance(dni).GetOrCreateTableConnection(srcProto, dstProto, addfmly) + tableConn1.SetImportPolicy([]string{policyName}) + if !deviations.SkipSettingDisableMetricPropagation(dut) { + tableConn1.SetDisableMetricPropagation(metricPropagation) + } + gnmi.BatchReplace(batchSet, gnmi.OC().NetworkInstance(dni).TableConnection(srcProto, dstProto, addfmly).Config(), tableConn1) } - gnmi.BatchReplace(batchSet, gnmi.OC().NetworkInstance(dni).TableConnection(srcProto, dstProto, addfmly).Config(), tableConn1) - } - batchSet.Set(t, dut) + batchSet.Set(t, dut) + } else if operation == "delete" { + gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(dni).TableConnection(srcProto, dstProto, addfmly).Config()) + } } func configureRoutePolicy(dut *ondatra.DUTDevice, rplName string, statement string, prefixSetCond, tagSetCond bool, @@ -690,8 +694,9 @@ func TestStaticToISISRedistribution(t *testing.T) { } t.Run(fmt.Sprintf("Attach RPL %v Type %v to ISIS %v", tc.RplName, tc.policyStmtType.String(), dni), func(t *testing.T) { - isisImportPolicyConfig(t, ts.DUT, tc.RplName, protoSrc, protoDst, tc.protoAf, tc.metricPropogation) + isisImportPolicyConfig(t, ts.DUT, tc.RplName, protoSrc, protoDst, tc.protoAf, tc.metricPropogation, "set") }) + defer isisImportPolicyConfig(t, ts.DUT, tc.RplName, protoSrc, protoDst, tc.protoAf, tc.metricPropogation, "delete") t.Run(fmt.Sprintf("Verify RPL %v Attributes", tc.RplName), func(t *testing.T) { getAndVerifyIsisImportPolicy(t, ts.DUT, tc.metricPropogation, tc.RplName, tc.protoAf.String()) diff --git a/feature/isis/otg_tests/weighted_ecmp_test/README.md b/feature/isis/otg_tests/weighted_ecmp_test/README.md index 6e2e44eb0ac..0261be0d350 100644 --- a/feature/isis/otg_tests/weighted_ecmp_test/README.md +++ b/feature/isis/otg_tests/weighted_ecmp_test/README.md @@ -152,6 +152,8 @@ paths: /network-instances/network-instance/protocols/protocol/isis/levels/level/config/metric-style: /network-instances/network-instance/protocols/protocol/isis/global/config/weighted-ecmp: /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/weighted-ecmp/config/load-balancing-weight: + /network-instances/network-instance/protocols/protocol/isis/global/config/max-ecmp-paths: + value: 3 /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/ip-prefix: /routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/masklength-range: value: exact diff --git a/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto b/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto index 8c66bd70839..ee536d51f02 100644 --- a/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto +++ b/feature/isis/otg_tests/weighted_ecmp_test/metadata.textproto @@ -40,5 +40,16 @@ platform_exceptions: { } deviations: { isis_level_enabled: true + predefined_max_ecmp_paths: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + missing_isis_interface_afi_safi_enable: true + explicit_interface_in_default_vrf: true + interface_enabled: true } } diff --git a/feature/isis/otg_tests/weighted_ecmp_test/weighted_ecmp_test.go b/feature/isis/otg_tests/weighted_ecmp_test/weighted_ecmp_test.go index b632f20483a..4c742db5c52 100644 --- a/feature/isis/otg_tests/weighted_ecmp_test/weighted_ecmp_test.go +++ b/feature/isis/otg_tests/weighted_ecmp_test/weighted_ecmp_test.go @@ -183,7 +183,7 @@ func TestWeightedECMPForISIS(t *testing.T) { var weight string switch dut.Vendor() { case ondatra.CISCO: - weight = fmt.Sprintf(" router isis DEFAULT \n interface %s \n address-family ipv4 unicast \n weight 100 \n address-family ipv6 unicast \n weight 100 \n ! \n interface %s \n address-family ipv4 unicast \n weight 100 \n address-family ipv6 unicast \n weight 100 \n ! \n interface %s \n address-family ipv4 unicast \n weight 100 \n address-family ipv6 unicast \n weight 100 \n", aggIDs[1], aggIDs[2], aggIDs[3]) + weight = fmt.Sprintf(" router isis DEFAULT \n interface %s \n address-family ipv4 unicast \n weight 100 \n address-family ipv6 unicast \n weight 100 \n ! \n interface %s \n address-family ipv4 unicast \n weight 100 \n address-family ipv6 unicast \n weight 100 \n ! \n interface %s \n address-family ipv4 unicast \n weight 100 \n address-family ipv6 unicast \n weight 100 \n", aggIDs[0], aggIDs[1], aggIDs[2]) default: t.Fatalf("Unsupported vendor %s for deviation 'WecmpAutoUnsupported'", dut.Vendor()) } @@ -236,7 +236,7 @@ func TestWeightedECMPForISIS(t *testing.T) { var weight string switch dut.Vendor() { case ondatra.CISCO: - weight = fmt.Sprintf(" router isis DEFAULT \n interface %s \n address-family ipv4 unicast \n weight 200 \n address-family ipv6 unicast \n weight 200 \n ! \n interface %s \n address-family ipv4 unicast \n weight 400 \n address-family ipv6 unicast \n weight 400 \n ! \n interface %s \n address-family ipv4 unicast \n weight 400 \n address-family ipv6 unicast \n weight 400 \n", aggIDs[1], aggIDs[2], aggIDs[3]) + weight = fmt.Sprintf(" router isis DEFAULT \n interface %s \n address-family ipv4 unicast \n weight 200 \n address-family ipv6 unicast \n weight 200 \n ! \n interface %s \n address-family ipv4 unicast \n weight 400 \n address-family ipv6 unicast \n weight 400 \n ! \n interface %s \n address-family ipv4 unicast \n weight 400 \n address-family ipv6 unicast \n weight 400 \n", aggIDs[0], aggIDs[1], aggIDs[2]) default: t.Fatalf("Unsupported vendor %s for deviation 'WecmpAutoUnsupported'", dut.Vendor()) } @@ -435,6 +435,9 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) []string { e.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB } gnmi.Replace(t, dut, dc.Interface(i1.GetName()).Config(), i1) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, i1.GetName(), deviations.DefaultNetworkInstance(dut), 0) + } var aggIDs []string for aggIdx, a := range []attrs.Attributes{dutagg1, dutagg2, dutagg3} { @@ -475,6 +478,9 @@ func configureDUT(t *testing.T, dut *ondatra.DUTDevice) []string { gnmi.BatchReplace(b, gnmi.OC().Interface(port.Name()).Config(), i) } b.Set(t, dut) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, aggID, deviations.DefaultNetworkInstance(dut), 0) + } } // Wait for LAG interfaces to be UP for _, aggID := range aggIDs { @@ -518,6 +524,11 @@ func configureDUTISIS(t *testing.T, dut *ondatra.DUTDevice, aggIDs []string) { 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) + var maxPaths uint8 = 3 + if deviations.PredefinedMaxEcmpPaths(dut) { + maxPaths = 16 + } + globalISIS.MaxEcmpPaths = &maxPaths lspBit := globalISIS.GetOrCreateLspBit().GetOrCreateOverloadBit() lspBit.SetBit = ygot.Bool(false) @@ -572,10 +583,6 @@ func VerifyISISTelemetry(t *testing.T, dut *ondatra.DUTDevice, dutIntfs []string t.Helper() statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_ISIS, isisInstance).Isis() for _, dutIntf := range dutIntfs { - - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - dutIntf = dutIntf + ".0" - } nbrPath := statePath.Interface(dutIntf) query := nbrPath.LevelAny().AdjacencyAny().AdjacencyState().State() _, ok := gnmi.WatchAll(t, dut, query, 3*time.Minute, func(val *ygnmi.Value[oc.E_Isis_IsisInterfaceAdjState]) bool { diff --git a/feature/lldp/feature.textproto b/feature/lldp/feature.textproto deleted file mode 100644 index 50ba028ca87..00000000000 --- a/feature/lldp/feature.textproto +++ /dev/null @@ -1,62 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "lldp" - version: 1 -} - -config_path { - path: "/lldp/config/enabled" -} -telemetry_path { - path: "/lldp/state/enabled" -} -config_path { - path: "/lldp/interfaces/interface/config/enabled" -} -telemetry_path { - path: "/lldp/interfaces/interface/state/enabled" -} -telemetry_path { - path: "/lldp/interfaces/interface/neighbors/neighbor/state/chassis-id" -} -telemetry_path { - path: "/lldp/interfaces/interface/neighbors/neighbor/state/chassis-id-type" -} -telemetry_path { - path: "/lldp/interfaces/interface/neighbors/neighbor/state/port-id" -} -telemetry_path { - path: "/lldp/interfaces/interface/neighbors/neighbor/state/port-id-type" -} -telemetry_path { - path: "/lldp/interfaces/interface/neighbors/neighbor/state/system-name" -} -config_path { - path: "/lldp/interfaces/interface/name" -} -telemetry_path { - path: "/lldp/interfaces/interface/state/name" -} -telemetry_path { - path: "/lldp/state/chassis-id" -} -telemetry_path { - path: "/lldp/state/chassis-id-type" -} -config_path { - path: "/lldp/config/system-name" -} -telemetry_path { - path: "/lldp/state/system-name" -} -config_path { - path: "/lldp/config/system-description" -} -telemetry_path { - path: "/lldp/state/system-description" -} -config_path { - path: "/lldp/interfaces/interface/config/name" -} 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 33322db9928..4d848e9b263 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 @@ -142,17 +142,20 @@ func configureDUT(t *testing.T, name string, lldpEnabled bool) (*ondatra.DUTDevi p := node.Port(t, portName) d := &oc.Root{} lldp := d.GetOrCreateLldp() - lldp.SystemDescription = ygot.String("DUT") + + if !deviations.MissingSystemDescriptionConfigPath(node) { + gnmi.Replace(t, node, gnmi.OC().Lldp().SystemDescription().Config(), "DUT") + } llint := lldp.GetOrCreateInterface(p.Name()) - llint.SetName(portName) + llint.SetName(p.Name()) + llint.Enabled = ygot.Bool(true) + // Enable lldp at interface level + gnmi.Replace(t, node, gnmi.OC().Lldp().Interface(p.Name()).Config(), llint) + // Configure lldp at root level gnmi.Replace(t, node, gnmi.OC().Lldp().Enabled().Config(), lldpEnabled) - if 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) } @@ -191,6 +194,7 @@ func configureATE(t *testing.T, otg *otg.OTG) gosnappi.Config { // verifyNodeConfig verifies the config by comparing against the telemetry state object. func verifyNodeConfig(t *testing.T, node gnmi.DeviceOrOpts, port *ondatra.Port, conf *oc.Lldp, lldpEnabled bool) { + dut := ondatra.DUT(t, "dut") if conf == nil { return } @@ -216,8 +220,10 @@ func verifyNodeConfig(t *testing.T, node gnmi.DeviceOrOpts, port *ondatra.Port, } else { t.Errorf("LLDP SystemName is not proper, got %s", state.GetSystemName()) } - if state.GetSystemDescription() != "DUT" { - t.Errorf("LLDP systemDescription is not proper, got %s", state.GetSystemDescription()) + if !deviations.MissingSystemDescriptionConfigPath(dut) { + if state.GetSystemDescription() != "DUT" { + t.Errorf("LLDP systemDescription is not proper, got %s", state.GetSystemDescription()) + } } got := state.GetInterface(port.Name()).GetName() diff --git a/feature/lldp/otg_tests/core_lldp_tlv_population_test/metadata.textproto b/feature/lldp/otg_tests/core_lldp_tlv_population_test/metadata.textproto index f6699729b78..7be7eb38a30 100644 --- a/feature/lldp/otg_tests/core_lldp_tlv_population_test/metadata.textproto +++ b/feature/lldp/otg_tests/core_lldp_tlv_population_test/metadata.textproto @@ -29,5 +29,6 @@ platform_exceptions: { deviations: { missing_value_for_defaults: true interface_enabled: true + missing_system_description_config_path: true } } diff --git a/feature/localaggregates/feature.textproto b/feature/localaggregates/feature.textproto deleted file mode 100644 index dc5c76ea7fa..00000000000 --- a/feature/localaggregates/feature.textproto +++ /dev/null @@ -1,43 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "localaggregates" - version: 1 -} - -# Local Route configuration(bgp aggregate, static inc default). -config_path { - path: "/network-instances/network-instance/protocols/protocol/local-aggregates/aggregate/config/prefix" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/local-aggregates/aggregate/config/discard" -} - -# Telemetry -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/local-aggregates/aggregate/state/prefix" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/local-aggregates/aggregate/state/discard" -} - -# BGP aggregates dependency -feature_profile_dependency { - name: "bgp" - version: 1 -} diff --git a/feature/mpls/otg_tests/mpls_tc/README.md b/feature/mpls/otg_tests/mpls_tc/README.md new file mode 100644 index 00000000000..e7b0db13948 --- /dev/null +++ b/feature/mpls/otg_tests/mpls_tc/README.md @@ -0,0 +1,143 @@ +# MPLS-1.2: MPLS Traffic Class Marking + +## Summary + +Verify MPLS Traffic Class Marking configuration. + +## Testbed type + +* [`featureprofiles/topologies/dutdut.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/dutdut.testbed) + +## Topology: + +```mermaid +graph LR; +A[DUT] <-- Port1/2(IPv4/6) --> B[DUT]; +``` + +## Procedure + +### Initial setup + +* Connect DUT-A port-1 to DUT-B port-2 + +* Configure IPv4 addresses on DUT ports + + * DUT-A port-1 IPv4 address ```dpa1-v4 = 192.168.1.1/30``` + * DUT-B port-2 IPv4 address ```dpb2-v4 = 192.168.1.2/30``` + +* Configure a loopback interface on both the DUT's and assign IPv4 addresses + + * DUT-A Loopback50 ```dpalo-v4 = 100.100.100.1/32``` + * DUT-B Loopback50 ```dpblo-v4 = 200.200.200.2/32``` + +* Configure ISIS between the DUTs and advertise Loopback50 + +* Enable MPLS and LDP on both the DUTs port-1 and port-2 + + * /network-instances/network-instance/mpls/global/interface-attributes/interface/config/mpls-enabled + * /network-instances/network-instance/mpls/signaling-protocols/ldp/global/config/lsr-id [DUT-A and B Loopkack50] + +### MPLS-1.2.1 - Configure and verify classifier to match MPLS packets and mark Traffic Class + +* Configure a classifier to match MPLS packets + + * /qos/classifiers/classifier/config/name = 'mpls-class' + * /qos/classifiers/classifier=[mpls-class]/config/type = 'MPLS' + * /qos/classifiers/classifier=[mpls-class]/terms/term/config/id = 'mpls-class-term' + * /qos/classifiers/classifier=[mpls-class]/terms/term=[mpls-class-term]/conditions/mpls/config/start-label-value = 16 + * /qos/classifiers/classifier=[mpls-class]/terms/term=[mpls-class-term]/conditions/mpls/config/end-label-value = 1048575 + +* Configure classifier to mark MPLS TC + + * /qos/classifiers/classifier=[mpls-class]/terms/term=[mpls-class-term]/actions/remark/config/set-mpls-tc = 5 + +* Apply the classifier on DUT-A port-1 + + * /qos/interfaces/interface/input/classifiers[mpls-class]/classifier/config/name + +* Validate the configuration is applied and values are reported correctly + + * /network-instances/network-instance/mpls/global/interface-attributes/interface/state/mpls-enabled + * /qos/classifiers/classifier/state/name = 'mpls-class' + * /qos/classifiers/classifier=[mpls-class]/state/type = 'MPLS' + * /qos/classifiers/classifier=[mpls-class]/terms/term/state/id = 'mpls-class-term' + * /qos/classifiers/classifier=[mpls-class]/terms/term=[mpls-class-term]/conditions/mpls/state/start-label-value = 16 + * /qos/classifiers/classifier=[mpls-class]/terms/term=[mpls-class-term]/conditions/mpls/state/end-label-value = 1048575 + * /qos/classifiers/classifier=[mpls-class]/terms/term=[mpls-class-term]/actions/remark/state/set-mpls-tc = 5 + + +## Canonical OC Configuration + +### QOS Classifier amd Marking MPLS packet + +```json +{ + "qos": { + "classifers": { + "classifier": { + "config": { + "name": "mpls-class", + "type": "MPLS" + }, + "terms": { + "term": { + "config": { + "id": "mpls-class-term" + }, + "conditions": { + "mpls": { + "config": { + "start-label-value": 16, + "end-label-value": 1048575 + } + } + }, + "actions": { + "config": { + "set-mpls-tc": 5 + } + } + } + } + } + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml + +paths: + +## Config paths: +/network-instances/network-instance/mpls/signaling-protocols/ldp/global/config/lsr-id: +/network-instances/network-instance/mpls/global/interface-attributes/interface/config/mpls-enabled: +/qos/classifiers/classifier/config/name: +/qos/classifiers/classifier/config/type: +/qos/classifiers/classifier/terms/term/config/id: +/qos/classifiers/classifier/terms/term/conditions/mpls/config/start-label-value: +/qos/classifiers/classifier/terms/term/conditions/mpls/config/end-label-value: +/qos/classifiers/classifier/terms/term/actions/remark/config/set-mpls-tc: + +## State paths: +/network-instances/network-instance/mpls/global/interface-attributes/interface/state/mpls-enabled: +/qos/classifiers/classifier/state/name: +/qos/classifiers/classifier/state/type: +/qos/classifiers/classifier/terms/term/state/id: +/qos/classifiers/classifier/terms/term/conditions/mpls/state/start-label-value: +/qos/classifiers/classifier/terms/term/conditions/mpls/state/end-label-value: +/qos/classifiers/classifier/terms/term/actions/remark/state/set-mpls-tc: + +rpcs: + gnmi: + gNMI.Set: + Replace: +``` + +## Required DUT platform + +* FFF + diff --git a/feature/mpls/otg_tests/mpls_tc/metadata.textproto b/feature/mpls/otg_tests/mpls_tc/metadata.textproto new file mode 100644 index 00000000000..81921b472d1 --- /dev/null +++ b/feature/mpls/otg_tests/mpls_tc/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "32ac61df-e182-4e2e-ab68-b517453dabbf" +plan_id: "MPLS-1.2" +description: "MPLS Traffic Class Marking" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/mpls/otg_tests/static_bgp_nexthop/README.md b/feature/mpls/otg_tests/static_bgp_nexthop/README.md index 7ed0a06fe99..1f11e9b309d 100644 --- a/feature/mpls/otg_tests/static_bgp_nexthop/README.md +++ b/feature/mpls/otg_tests/static_bgp_nexthop/README.md @@ -20,58 +20,15 @@ Validate static LSP functionality with BGP resolved next-hop. This test verifies | | ---- | ATE Port 3 | ``` -2) Configure eBGP peer on ATE Port 2 interface and advertise `BGP-NH-V4= 203.0.200.0/24` and `BGP-NH-V6= 2001:db8:128:200::/64` -3) Configure static routes on the DUT to discard traffic destined for BGP-NH-V4 and BGP-NH-V6. These routes should point to a Null0 with an administrative distance of 254 to ensure they are less preferred than the BGP routes. This prevents the DUT from using its IGP to reach the BGP next-hops. -4) Enable MPLS forwarding. -5) Create egress static LSP for IPv4 and IPV6 traffic to pop the label and resolve the next-hop BGP-NH-V4 and BGP-NH-V6 respectivelly +2) Configure eBGP peer on ATE Port 2 interface and advertise `BGP-NH-V4=198.51.100.0/24` and `BGP-NH-V6=2001:DB8:100::0/64` +3) Enable MPLS forwarding. +4) Create egress static LSP for IPv4 and IPV6 traffic to pop the label and resolve the next-hop BGP-NH-V4 and BGP-NH-V6 respectivelly -```yaml -network-instances: - - network-instance: - mpls: - lsps: - static-lsps: - - static-lsp: - config: - name: "lsp-egress-v4" - egress: - next-hop: 203.0.200.1 - incoming-label: 1000004 - - static-lsp: - config: - name: "lsp-egress-v6" - egress: - next-hop: 2001:db8:128:200::1 - incoming-label: 1000006 -``` - * Set resolve NH action for both LSPs. +* Set resolve NH action for both LSPs. **TODO:** OC model does not support resolve next-hop option for LSPs. -7) Configure static routes i.e. `IPV4-DST = 203.0.113.0/24` and `IPV6-DST = 2001:db8:128:128::/64` to ATE Port 3. -```yaml -network-instances: - - network-instance: - protocols: - - protocol: - static-routes: - - static: - config: - prefix: "203.0.113.0/24" - next-hops: - - next-hop: - config: - index: 1 - next-hop: "ATE PORT 3" - - static: - config: - prefix: "2001:db8:128:128::/64" - next-hops: - - next-hop: - config: - index: 1 - next-hop: "ATE PORT 3" -``` +7) Configure static routes i.e. `IPV4-DST = 198.51.100.0/24` and `IPV6-DST = 2001:DB8:100::0/64` to ATE Port 3 with administrative distance (preference) 254. ### MPLS-2.2.1: Verify IPv4 MPLS forwarding @@ -90,15 +47,66 @@ network-instances: * Withdraw BGP-NH-V4 advertisement. * Push the above DUT configuration. * Start traffic flow with MPLS[lbl-1000004] and IPv4 destination set to IPV4-DST. -* Verify that traffic is discarded. +* Verify that traffic is forwarded to ATE Port 3. ### MPLS-2.2.4: Verify IPv6 traffic discard when BGP-NH is not available. * Withdraw BGP-NH-V6 advertisement. * Push the above DUT configuration. * Start traffic flow with MPLS[lbl-1000006] and IPv6 destination set to IPV6-DST. -* Verify that traffic is discarded. - +* Verify that traffic is forwarded to ATE Port 3. + +## Canonical OC +OC for a static MPLS LSP is provided here. + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "mpls": { + "lsps": { + "static-lsps": { + "static-lsp": [ + { + "config": { + "name": "lspv4" + }, + "egress": { + "config": { + "incoming-label": 10004, + "next-hop": "203.0.200.1", + "push-label": "IMPLICIT_NULL" + } + }, + "name": "lspv4" + }, + { + "config": { + "name": "lspv6" + }, + "egress": { + "config": { + "incoming-label": 10006, + "next-hop": "2001:db8:128:200::1", + "push-label": "IMPLICIT_NULL" + } + }, + "name": "lspv6" + } + ] + } + } + }, + "name": "DEFAULT" + } + ] + } +} +``` ## OpenConfig Path and RPC Coverage ```yaml @@ -118,4 +126,4 @@ rpcs: replace: true gNMI.Subscribe: on_change: true -``` \ No newline at end of file +``` diff --git a/feature/mpls/otg_tests/static_bgp_nexthop/metadata.textproto b/feature/mpls/otg_tests/static_bgp_nexthop/metadata.textproto new file mode 100644 index 00000000000..8bd88c9c3d4 --- /dev/null +++ b/feature/mpls/otg_tests/static_bgp_nexthop/metadata.textproto @@ -0,0 +1,18 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "33947478-0473-4832-8717-6176d4e7a406" +plan_id: "MPLS-2.2" +description: "MPLS forwarding via static LSP to BGP next-hop." +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + static_mpls_unsupported: true + static_mpls_lsp_oc_unsupported: true + } +} diff --git a/feature/mpls/otg_tests/static_bgp_nexthop/static_bgp_nexthop_test.go b/feature/mpls/otg_tests/static_bgp_nexthop/static_bgp_nexthop_test.go new file mode 100644 index 00000000000..258ff75052c --- /dev/null +++ b/feature/mpls/otg_tests/static_bgp_nexthop/static_bgp_nexthop_test.go @@ -0,0 +1,506 @@ +package static_bgp_nexthop_test + +import ( + "math" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + bgpNHv4 = "203.0.200.1" + bgpNHv6 = "2001:db8:128:200::1" + BGPNHV4 = "203.0.200.0/24" + BGPNHV6 = "2001:db8:128:200::/64" + mplsLabelV4 = 10004 // Arista not suppoting the range mentioned in readme + mplsLabelV6 = 10006 // Arista not suppoting the range mentioned in readme + ipv4Dst = "203.0.113.0/24" + ipv6Dst = "2001:db8:128:128::/64" + iPV4Dst = "203.0.113.1" + iPV6Dst = "2001:db8:128:128::1" + dutAS = 65001 + ateAS = 65002 + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + adDist = 254 + rplType = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + rplName = "ALLOW" + trafficPps = 1000 + trafficSize = 1500 + bgpV4PeerName = "BGP-PEER-GROUP-V4-P2" + bgpV6PeerName = "BGP-PEER-GROUP-V6-P2" + lspV4Name = "lsp-egress-v4" + lspV6Name = "lsp-egress-v6" + ipv4Flow = "Ipv4_Mpls" + ipv6Flow = "Ipv6_Mpls" + lossTolerance = 2 + trafficDuration = 30 +) + +var ( + atePort1 = attrs.Attributes{ + Name: "ateP1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort2 = attrs.Attributes{ + Name: "ateP2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort3 = attrs.Attributes{ + Name: "ateP3", + MAC: "02:00:03:01:01:01", + IPv4: "192.0.2.10", + IPv6: "2001:db8::10", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutPort1 = attrs.Attributes{ + Desc: "DUT to ATE source", + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT to ATE destination-2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutPort3 = attrs.Attributes{ + Desc: "DUT to ATE destination-3", + IPv4: "192.0.2.9", + IPv6: "2001:db8::9", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + timeout = 1 * time.Minute + interval = 20 * time.Second +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Test cases: +// 1) Verify IPv4 MPLS forwarding. +// - Push the above DUT configuration. +// - Start traffic flow with MPLS[lbl-1000004] and IPv4 destined to IPV4-DST. +// - Verify that traffic arrives to ATE Port 2. +// 2) Verify IPv6 MPLS forwarding. +// ` - Push the above DUT configuration. +// - Start traffic flow with MPLS[lbl-1000006] and IPv4 destined to IPV6-DST. +// - Verify that traffic arrives to ATE Port 2. +// 3) Verify IPv4 traffic discard when BGP-NH is not available. +// - Withdraw BGP-NH-V4 advertisement. +// - Push the above DUT configuration. +// - Start traffic flow with MPLS[lbl-1000004] and IPv4 destination set to IPV4-DST. +// - Verify that traffic is discarded. +// 4) Verify IPv6 traffic discard when BGP-NH is not available. +// - Withdraw BGP-NH-V6 advertisement. +// - Push the above DUT configuration. +// - Start traffic flow with MPLS[lbl-1000006] and IPv6 destination set to IPV6-DST. +// - Verify that traffic is discarded. +// Details: https://github.com/openconfig/featureprofiles/blob/main/feature/mpls/otg_tests/static_bgp_nexthop/README.md +// + +// TestMplsStaticLspBGPNextHop runs the verification steps +func TestMplsStaticLspBGPNextHop(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + configureDUT(t, dut) + ateConfig := configureATE(t) + sfBatch := &gnmi.SetBatch{} + cfgplugins.MPLSStaticLSP(t, sfBatch, dut, lspV4Name, mplsLabelV4, bgpNHv4, "", "ipv4") + cfgplugins.MPLSStaticLSP(t, sfBatch, dut, lspV6Name, mplsLabelV6, bgpNHv6, "", "ipv6") + sfBatch.Set(t, dut) + verifyPortsUp(t, dut.Device) + + // TODO: 409240869 - Discard test added based on README guidance; will update if needed once the bug is fixed. + buildIPv4MPLSFlow(t, ateConfig, ipv4Flow, iPV4Dst) + buildIPv6MPLSFlow(t, ateConfig, ipv6Flow, iPV6Dst) + + // Push config and start protocols once + ate.OTG().PushConfig(t, ateConfig) + ate.OTG().StartProtocols(t) + + // Wait for ARP resolution once + otgutils.WaitForARP(t, ate.OTG(), ateConfig, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), ateConfig, "IPv6") + + // Check BGP Establish + checkBgpStatus(t, dut) + t.Run("Verify IPv4 MPLS forwarding", func(t *testing.T) { + verifyMPLSForwarding(t, ate, ateConfig, dut, ipv4Flow, false, BGPNHV4, adDist, "0") + }) + + t.Run("Verify IPv6 MPLS forwarding", func(t *testing.T) { + verifyMPLSForwarding(t, ate, ateConfig, dut, ipv6Flow, true, BGPNHV6, adDist, "1") + }) + t.Run("Verify IPv4 traffic discard when BGP-NH-V4 is not available.", func(t *testing.T) { + addStaticRouteWithNexthop(t, dut, ipv4Dst, atePort3.IPv4, "2") + withdrawBGPRoutes(t, []string{"BGP4.peer2.Route"}) + verifyIpv4TrafficDiscard(t, ate, ateConfig, dut, ipv4Flow) + }) + t.Run("Verify IPv6 traffic discard when BGP-NH-V6 is not available.", func(t *testing.T) { + addStaticRouteWithNexthop(t, dut, ipv6Dst, atePort3.IPv6, "3") + withdrawBGPRoutes(t, []string{"BGP6.peer2.Route"}) + verifyIpv6TrafficDiscard(t, ate, ateConfig, dut, ipv6Flow) + }) +} + +func verifyMPLSForwarding(t *testing.T, ate *ondatra.ATEDevice, ateConfig gosnappi.Config, dut *ondatra.DUTDevice, flow string, isIPv6 bool, bgpNH string, adDist int, staticIndex string) { + // Verify MPLS forwarding (BGP active) + ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration * time.Second) + ate.OTG().StopTraffic(t) + if isIPv6 { + if verifyFlowTraffic(t, ate, ateConfig, flow) { + t.Log("IPv6 Traffic MPLS forwarding Passed") + } else { + t.Error("IPv6 Traffic MPLS forwarding Failed") + } + // Configure static route to Null0 and update BGPv6 + // TODO: 409240869 - Route to Null0 with administrative distance was not explicitly added as per clarification; implemented based on README guidance. Will update if required once the bug is fixed. + addStaticRouteWithAD(t, dut, bgpNH, adDist, staticIndex) + } else { + if verifyFlowTraffic(t, ate, ateConfig, flow) { + t.Log("IPv4 Traffic MPLS forwarding Passed") + } else { + t.Error("IPv4 Traffic MPLS forwarding Failed") + } + // Configure static route to Null0 and update BGPv4 + addStaticRouteWithAD(t, dut, bgpNH, adDist, staticIndex) + } +} + +// configureDUT sets up the DUT interfaces, static LSPs, and BGP neighbors. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := gnmi.OC() + // Configure interfaces + p1 := dut.Port(t, "port1").Name() + i1 := dutPort1.NewOCInterface(p1, dut) + gnmi.Replace(t, dut, d.Interface(p1).Config(), i1) + + p2 := dut.Port(t, "port2").Name() + i2 := dutPort2.NewOCInterface(p2, dut) + gnmi.Replace(t, dut, d.Interface(p2).Config(), i2) + + p3 := dut.Port(t, "port3").Name() + i3 := dutPort3.NewOCInterface(p3, dut) + gnmi.Replace(t, dut, d.Interface(p3).Config(), i3) + fptest.ConfigureDefaultNetworkInstance(t, dut) + + configureRoutePolicy(t, dut, rplName, rplType) + dutConfPath := d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + dutConf := createBGPNeighborPort2(dutAS, ateAS, dut, true, true) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) +} + +// configureATE sets up the ATE interfaces and BGP configurations. +func configureATE(t *testing.T) gosnappi.Config { + topo := gosnappi.NewConfig() + t.Log("Configure ATE interface") + port1 := topo.Ports().Add().SetName("port1") + port2 := topo.Ports().Add().SetName("port2") + port3 := topo.Ports().Add().SetName("port3") + + port1Dev := topo.Devices().Add().SetName(atePort1.Name + ".dev") + port1Eth := port1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + port1Eth.Connection().SetPortName(port1.Name()) + port1Ipv4 := port1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + port1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + port1Ipv6 := port1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + port1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + port2Dev := topo.Devices().Add().SetName(atePort2.Name + ".dev") + port2Eth := port2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + port2Eth.Connection().SetPortName(port2.Name()) + port2Ipv4 := port2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + port2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + port2Ipv6 := port2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + port2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + port3Dev := topo.Devices().Add().SetName(atePort3.Name + ".dev") + port3Eth := port3Dev.Ethernets().Add().SetName(atePort3.Name + ".Eth").SetMac(atePort3.MAC) + port3Eth.Connection().SetPortName(port3.Name()) + port3Ipv4 := port3Eth.Ipv4Addresses().Add().SetName(atePort3.Name + ".IPv4") + port3Ipv4.SetAddress(atePort3.IPv4).SetGateway(dutPort3.IPv4).SetPrefix(uint32(atePort3.IPv4Len)) + port3Ipv6 := port3Eth.Ipv6Addresses().Add().SetName(atePort3.Name + ".IPv6") + port3Ipv6.SetAddress(atePort3.IPv6).SetGateway(dutPort3.IPv6).SetPrefix(uint32(atePort3.IPv6Len)) + // Configure BGP on ATE Port 2 to advertise next-hop prefixes + bgproute := port2Dev.Bgp().SetRouterId(atePort2.IPv4) + bgpPeerv4 := bgproute.Ipv4Interfaces().Add().SetIpv4Name(port2Ipv4.Name()).Peers().Add().SetName("BGP4.peer2") + bgpPeerv4.SetPeerAddress(port2Ipv4.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + bgpPeerv6 := bgproute.Ipv6Interfaces().Add().SetIpv6Name(port2Ipv6.Name()).Peers().Add().SetName("BGP6.peer2") + bgpPeerv6.SetPeerAddress(port2Ipv6.Gateway()).SetAsNumber(ateAS).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + bgpPeerv4Routes := bgpPeerv4.V4Routes().Add().SetName("BGP4.peer2.Route") + bgpPeerv4Routes.Addresses().Add().SetAddress(bgpNHv4).SetPrefix(24) + + bgpPeerv6Routes := bgpPeerv6.V6Routes().Add().SetName("BGP6.peer2.Route") + bgpPeerv6Routes.Addresses().Add().SetAddress(bgpNHv6).SetPrefix(64) + + return topo +} + +func buildIPv4MPLSFlow(t *testing.T, config gosnappi.Config, flowname, ipv4Dt string) { + dut := ondatra.DUT(t, "dut") + macAddress := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + + flow := config.Flows().Add() + flow.SetName(flowname) + flow.Metrics().SetEnable(true) + flow.TxRx().Port(). + SetTxName(config.Ports().Items()[0].Name()). + SetRxNames([]string{config.Ports().Items()[1].Name(), config.Ports().Items()[2].Name()}) + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(atePort1.MAC) + eth.Dst().SetValue(macAddress) + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(mplsLabelV4) + ip4 := flow.Packet().Add().Ipv4() + ip4.Src().SetValue(atePort1.IPv4) + ip4.Dst().SetValue(ipv4Dt) + flow.Size().SetFixed(trafficSize) + flow.Rate().SetPps(trafficPps) +} + +func buildIPv6MPLSFlow(t *testing.T, config gosnappi.Config, flowname, ipv6Dt string) { + dut := ondatra.DUT(t, "dut") + macAddress := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + + flow := config.Flows().Add() + flow.SetName(flowname) + flow.Metrics().SetEnable(true) + flow.TxRx().Port(). + SetTxName(config.Ports().Items()[0].Name()). + SetRxNames([]string{config.Ports().Items()[1].Name(), config.Ports().Items()[2].Name()}) + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(atePort1.MAC) + eth.Dst().SetValue(macAddress) + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(mplsLabelV6) + ip6 := flow.Packet().Add().Ipv6() + ip6.Src().SetValue(atePort1.IPv6) + ip6.Dst().SetValue(ipv6Dt) + ip6.Version().SetValue(6) + flow.Size().SetFixed(trafficSize) + flow.Rate().SetPps(trafficPps) +} + +// Verify Ipv4 Traffic Discard +func verifyIpv4TrafficDiscard(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, dut *ondatra.DUTDevice, flow string) { + t.Log("Verify traffic discard via Null0") + ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration * time.Second) + ate.OTG().StopTraffic(t) + if verifyFlowTraffic(t, ate, config, flow) == false { + t.Log("Packets correctly discarded via Null0 when BGPv4 down") + } else { + t.Error("Unexpected packets received, DUT may not be discarding via Null0") + } +} + +// Verify Ipv6 Traffic Discard +func verifyIpv6TrafficDiscard(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, dut *ondatra.DUTDevice, flow string) { + t.Log("Verify traffic discard via Null0") + ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration * time.Second) + ate.OTG().StopTraffic(t) + if verifyFlowTraffic(t, ate, config, flow) == false { + t.Log("Packets correctly discarded via Null0 when BGPv6 down") + } else { + t.Error("Unexpected packets received, DUT may not be discarding via Null0") + } +} + +func verifyFlowTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, flowName string) bool { + t.Log("Verify Flow Traffic") + startTime := time.Now() + count := 0 + var got float64 + for time.Since(startTime) < timeout { + + otgutils.LogFlowMetrics(t, ate.OTG(), config) + countersPath := gnmi.OTG().Flow(flowName).Counters() + framesRx := gnmi.Get(t, ate.OTG(), countersPath.InPkts().State()) + framesTx := gnmi.Get(t, ate.OTG(), countersPath.OutPkts().State()) + + if got = (math.Abs(float64(framesTx)-float64(framesRx)) * 100) / float64(framesTx); got <= lossTolerance { + return true + } else { + time.Sleep(interval) + count += 1 + } + } + + if count >= 2 { + t.Logf("Packet loss percentage for flow: got %v, want %v", got, lossTolerance) + return false + } + return true +} + +// configreRoutePolicy adds route-policy config +func configureRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, name string, pr oc.E_RoutingPolicy_PolicyResultType) { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pd := rp.GetOrCreatePolicyDefinition(name) + st, err := pd.AppendNewStatement("id-1") + if err != nil { + t.Fatal(err) + } + st.GetOrCreateActions().PolicyResult = pr + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) +} + +// Configure BGP Neighbors on DUT +func createBGPNeighborPort2(localAs, peerAs uint32, dut *ondatra.DUTDevice, v4shut, v6shut bool) *oc.NetworkInstance_Protocol { + d := &oc.Root{} + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.As = ygot.Uint32(localAs) + global.RouterId = ygot.String(dutPort2.IPv4) + + 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) + + pgv4 := bgp.GetOrCreatePeerGroup(bgpV4PeerName) + pgv4.PeerAs = ygot.Uint32(peerAs) + pgv4.PeerGroupName = ygot.String(bgpV4PeerName) + pgv6 := bgp.GetOrCreatePeerGroup(bgpV6PeerName) + pgv6.PeerAs = ygot.Uint32(peerAs) + pgv6.PeerGroupName = ygot.String(bgpV6PeerName) + nv4 := bgp.GetOrCreateNeighbor(atePort2.IPv4) + nv4.PeerAs = ygot.Uint32(peerAs) + nv4.Enabled = ygot.Bool(true) + nv4.PeerGroup = ygot.String(bgpV4PeerName) + nv4.Enabled = ygot.Bool(v4shut) + afisafi := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afisafi.Enabled = ygot.Bool(true) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(false) + pgafv4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pgafv4.Enabled = ygot.Bool(true) + rpl4 := pgafv4.GetOrCreateApplyPolicy() + rpl4.ImportPolicy = []string{rplName} + rpl4.ExportPolicy = []string{rplName} + nv6 := bgp.GetOrCreateNeighbor(atePort2.IPv6) + nv6.PeerAs = ygot.Uint32(peerAs) + nv6.Enabled = ygot.Bool(true) + nv6.PeerGroup = ygot.String(bgpV6PeerName) + nv6.Enabled = ygot.Bool(v6shut) + afisafi6 := nv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + afisafi6.Enabled = ygot.Bool(true) + nv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(false) + pgafv6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pgafv6.Enabled = ygot.Bool(true) + rpl6 := pgafv6.GetOrCreateApplyPolicy() + rpl6.ImportPolicy = []string{rplName} + rpl6.ExportPolicy = []string{rplName} + + return niProto +} + +// Validate BGP neighbor established +func checkBgpStatus(t *testing.T, dut *ondatra.DUTDevice) { + t.Log("Verifying BGP state") + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + nbrPath := statePath.Neighbor(atePort2.IPv4) + nbrPathv6 := statePath.Neighbor(atePort2.IPv6) + + // Get BGP adjacency state + t.Log("Waiting for BGP neighbor to establish...") + _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + currState, ok := val.Val() + return ok && currState == 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("BGP session not Established as expected...") + } + t.Log("BGP sessions established") + // Get BGPv6 adjacency state + t.Log("Waiting for BGPv6 neighbor to establish...") + _, ok = gnmi.Watch(t, dut, nbrPathv6.SessionState().State(), 2*time.Minute, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + currState, ok := val.Val() + return ok && currState == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + fptest.LogQuery(t, "BGPv6 reported state", nbrPathv6.State(), gnmi.Get(t, dut, nbrPathv6.State())) + t.Fatal("BGPv6 session not Established as expected...") + } + t.Log("BGPv6 sessions established") +} + +// addStaticRoute configures static route. +func addStaticRouteWithAD(t *testing.T, dut *ondatra.DUTDevice, staticIp string, addst int, hopIndex string) { + d := gnmi.OC() + s := &oc.Root{} + static := s.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + ipNh := static.GetOrCreateStatic(staticIp).GetOrCreateNextHop(hopIndex) + ipNh.NextHop = oc.UnionString("DROP") // Use "DROP" instead of "Null0" + ipNh.SetPreference(uint32(addst)) + gnmi.Update(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) +} + +// addStaticRoute configures static route. +func addStaticRouteWithNexthop(t *testing.T, dut *ondatra.DUTDevice, staticIp string, ipDST, hopIndex string) { + d := gnmi.OC() + s := &oc.Root{} + static := s.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)) + ipNh := static.GetOrCreateStatic(staticIp).GetOrCreateNextHop(hopIndex) + ipNh.NextHop, _ = ipNh.To_NetworkInstance_Protocol_Static_NextHop_NextHop_Union(ipDST) + gnmi.Update(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_STATIC, deviations.StaticProtocolName(dut)).Config(), static) +} + +func withdrawBGPRoutes(t *testing.T, routeNames []string) { + defer func() { + if r := recover(); r != nil { + t.Errorf("Failed to withdraw BGP routes: %v", r) + } + }() + + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Protocol().Route().SetNames(routeNames).SetState(gosnappi.StateProtocolRouteState.WITHDRAW) + otg.SetControlState(t, cs) + t.Log("BGP routes withdrawn successfully") +} + +// Verify ports status +func verifyPortsUp(t *testing.T, dev *ondatra.Device) { + t.Helper() + t.Log("Verifying port status") + for _, p := range dev.Ports() { + status := gnmi.Get(t, dev, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_UP; status != want { + t.Errorf("%s Status: got %v, want %v", p, status, want) + } + } +} diff --git a/feature/mpls/otg_tests/static_lsp_test/metadata.textproto b/feature/mpls/otg_tests/static_lsp_test/metadata.textproto index 5d5dbf8904f..0fb66e56e1b 100644 --- a/feature/mpls/otg_tests/static_lsp_test/metadata.textproto +++ b/feature/mpls/otg_tests/static_lsp_test/metadata.textproto @@ -11,5 +11,6 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + use_old_oc_path_static_lsp_nh: true } } diff --git a/feature/mpls/otg_tests/static_lsp_test/static_lsp_test.go b/feature/mpls/otg_tests/static_lsp_test/static_lsp_test.go index 6d6db8bf094..af3bbc8b694 100644 --- a/feature/mpls/otg_tests/static_lsp_test/static_lsp_test.go +++ b/feature/mpls/otg_tests/static_lsp_test/static_lsp_test.go @@ -148,8 +148,15 @@ func configureStaticLSP(t *testing.T, dut *ondatra.DUTDevice, lspName string, in mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() staticMplsCfg := mplsCfg.GetOrCreateLsps().GetOrCreateStaticLsp(lspName) staticMplsCfg.GetOrCreateEgress().SetIncomingLabel(oc.UnionUint32(incomingLabel)) - staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetIpAddress(nextHopIP) - staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetPushLabel(oc.UnionUint32(implicitNull)) + // use old path for next hop if deviation used. + if deviations.UseOldOCPathStaticLspNh(dut) { + staticMplsCfg.GetOrCreateEgress().SetNextHop(nextHopIP) + staticMplsCfg.GetOrCreateEgress().SetPushLabel(oc.Egress_PushLabel_IMPLICIT_NULL) + } else { + staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetIpAddress(nextHopIP) + staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetPushLabel(oc.UnionUint32(implicitNull)) + + } gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) } diff --git a/feature/mpls/sr/otg_tests/isis_node_sid_forward/README.md b/feature/mpls/sr/otg_tests/isis_node_sid_forward/README.md index 394f59200a1..4bc2f8a05e5 100644 --- a/feature/mpls/sr/otg_tests/isis_node_sid_forward/README.md +++ b/feature/mpls/sr/otg_tests/isis_node_sid_forward/README.md @@ -5,15 +5,19 @@ MPLS-SR transit forwarding to Node-SID distributed over ISIS ## Testbed type - -* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Topology +```mermaid +graph LR; +A[ATE PORT1] <-- IPv4-IPv6 --> B[DUT PORT1]; +C[ATE:PORT2] <-- IPv4-IPv6 --> D[DUT PORT2]; +E[ATE:PORT3] <-- IPv4-IPv6 --> F[DUT PORT3]; +G[ATE:PORT4] <-- IPv4-IPv6 --> H[DUT PORT4]; +``` ## Procedure - ### Configuration - -Topology: ATE1—DUT1–ATE2 - * Configure Segment Routing Global Block (srgb) lower-bound: 400000 upper-bound: 465001) * Enable Segment Routing for the ISIS * Enable MPLS forwarding. @@ -21,36 +25,156 @@ Topology: ATE1—DUT1–ATE2 * Prefix (1) with node-SID is advertised by the direct ISIS neighbor * Prefix (2) with node-SID is advertised by simulated indirect ISIS speaker -### Test +* ATE port1 - used for Traffic source +* ATE port2-port4 - used for verification of per-interface sid or per-node sid + +### Canonical OC for DUT configuration +This section should contain a JSON formatted stanza representing the +canonical OC to configure MPLS SR-ID (See the +[README Template](https://github.com/openconfig/featureprofiles/blob/main/doc/test-requirements-template.md#procedure)) +``` +{ + "openconfig-network-instance:network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT", + "type": "openconfig-network-instance-types:DEFAULT_INSTANCE" + }, + "mpls": { + "global": { + "reserved-label-blocks": { + "reserved-label-block": [ + { + "config": { + "local-id": "srlb", + "lower-bound": 16 + }, + "local-id": "srlb" + }, + { + "config": { + "local-id": "isis-sr", + "lower-bound": 400000, + "upper-bound": 465000 + }, + "local-id": "isis-sr" + } + ] + } + } + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "openconfig-policy-types:ISIS", + "name": "isis", + "config": { + "identifier": "openconfig-policy-types:ISIS", + "name": "isis" + }, + "isis": { + "global": { + "segment-routing": { + "config": { + "enabled": true, + "srgb": "isis-sr", + "srlb": "srlb" + } + } + } + } + } + ] + }, + "segment-routing": { + "srgbs": { + "srgb": [ + { + "config": { + "dataplane-type": "MPLS", + "local-id": "isis-sr", + "mpls-label-blocks": [ + "isis-sr" + ] + }, + "local-id": "isis-sr" + } + ] + }, + "srlbs": { + "srlb": [ + { + "config": { + "dataplane-type": "MPLS", + "local-id": "srlb", + "mpls-label-block": "srlb" + }, + "local-id": "srlb" + } + ] + } + } + } + ] + } +} +``` -Verify that: +### Test +On DUT1 configure: -* DUT advertises both prefixes with node-SID to ATE2. +* ISIS adjacency between ATE & DUT for all the ports ATE1, ATE2, ATE3, ATE4; DUT1, DUT2, DUT3, DUT4. +* Enable MPLS-SR for ISIS (`/network-instances/network-instance/protocols/protocol/isis/global/segment-routing/config/enabled`) for each + interface +* reserved-label-block (lower-bound: 1000000 upper-bound: 1048576) +* Segment Routing Global Block (srgb) with lower-bound: 400000 upper-bound: 465001 +* Segment Routing Local Block (srlb) with lower-bound: 40000 upper-bound: 41000) +* Send traffic from Source to destination and make sure the interface sid counters are populated Generate traffic: * Send labeled traffic transiting through the DUT matching direct prefix (1). Verify that ATE2 receives traffic with node-SID label popped. * Send labeled traffic transiting through the DUT matching indirect prefix (2). Verify that ATE2 receives traffic with the node-SID label intact. * Verify that corresponding SID forwarding counters are incremented. * Traffic arrives without packet loss. + +Verify: +* Defined blocks are configured on DUT1. +* DUT1 advertises its SRGB and SRLB to ATE1. +* Verify the Interface SID counters ## OpenConfig Path and RPC Coverage ```yaml paths: # srgb definition + /network-instances/network-instance/mpls/global/interface-attributes/interface/config/mpls-enabled: /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/config/local-id: /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/config/lower-bound: /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/config/upper-bound: + # sr config - /network-instances/network-instance/mpls/global/interface-attributes/interface/config/mpls-enabled: /network-instances/network-instance/segment-routing/srgbs/srgb/config/local-id: /network-instances/network-instance/segment-routing/srgbs/srgb/config/mpls-label-blocks: + /network-instances/network-instance/segment-routing/srlbs/srlb/local-id: + /network-instances/network-instance/segment-routing/srlbs/srlb/config/mpls-label-block: /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/config/enabled: /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/config/srgb: + /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/config/srlb: + /network-instances/network-instance/mpls/signaling-protocols/segment-routing/interfaces/interface/config/interface-id: + # telemetry /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/state/enabled: /network-instances/network-instance/mpls/signaling-protocols/segment-routing/aggregate-sid-counters/aggregate-sid-counter/state/in-pkts: /network-instances/network-instance/mpls/signaling-protocols/segment-routing/aggregate-sid-counters/aggregate-sid-counter/state/out-pkts: + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/state/local-id: + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/state/lower-bound: + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/state/upper-bound: + /network-instances/network-instance/mpls/signaling-protocols/segment-routing/interfaces/interface/state/interface-id: + /network-instances/network-instance/mpls/signaling-protocols/segment-routing/interfaces/interface/sid-counters/sid-counter/state/in-pkts: + /network-instances/network-instance/mpls/signaling-protocols/segment-routing/interfaces/interface/sid-counters/sid-counter/state/out-pkts: + rpcs: gnmi: @@ -60,6 +184,8 @@ rpcs: gNMI.Subscribe: on_change: true ``` + ## Required DUT platform * FFF + diff --git a/feature/mpls/sr/otg_tests/isis_node_sid_forward/isis_node_sid_forward_test.go b/feature/mpls/sr/otg_tests/isis_node_sid_forward/isis_node_sid_forward_test.go index e08bb1a8b99..0fa8f5c0a1b 100644 --- a/feature/mpls/sr/otg_tests/isis_node_sid_forward/isis_node_sid_forward_test.go +++ b/feature/mpls/sr/otg_tests/isis_node_sid_forward/isis_node_sid_forward_test.go @@ -72,6 +72,9 @@ func configureISISSegmentRouting(t *testing.T, ts *isissession.TestSession) { mplsCfg.GetOrCreateReservedLabelBlock(srgbMplsLabelBlockName).LowerBound = oc.UnionUint32(srgbLowerBound) mplsCfg.GetOrCreateReservedLabelBlock(srgbMplsLabelBlockName).UpperBound = oc.UnionUint32(srgbUpperBound) + mplsCfgIntf := mplsCfg.GetOrCreateInterface(ts.DUTPort1.Name()) + mplsCfgIntf.InterfaceId = ygot.String(ts.DUTPort1.Name()) + // Configure SR srCfg := networkInstance.GetOrCreateSegmentRouting() srgb := srCfg.GetOrCreateSrgb("99.99.99.99") @@ -172,6 +175,27 @@ func verifyMPLSSR(t *testing.T, ts *isissession.TestSession) { }) } +func verifySRCounters(t *testing.T, ts *isissession.TestSession, ate *ondatra.ATEDevice) { + d := ts.DUTConf + networkInstance := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(ts.DUT)) + recvMetricV4 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v4FlowName).State()) + // recvMetricV6 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v6FlowName).State()) + v4InPkts := recvMetricV4.GetCounters().GetInPkts() + v4OutPkts := recvMetricV4.GetCounters().GetOutPkts() + // Get SR Counters + srSgProto := networkInstance.GetOrCreateMpls().GetOrCreateSignalingProtocols().GetSegmentRouting() + srIntf := srSgProto.GetOrCreateInterface(ts.DUTPort1.Name()) + t.Logf("SR InPkts: %d, SR OutPkts: %d", srIntf.InPkts, srIntf.OutPkts) + t.Logf("InPkts: %d, OutPkts: %d", v4InPkts, v4OutPkts) + + if got := srIntf.InPkts; got != ygot.Uint64(0) { + t.Errorf("FAIL- SR InPkts is not zero, got %d, want %d", got, v4InPkts) + } + if got := srIntf.OutPkts; got != ygot.Uint64(0) { + t.Errorf("FAIL- SR OutPkts is not zero, got %d, want %d", got, v4OutPkts) + } +} + func verifyTraffic(t *testing.T, ate *ondatra.ATEDevice) { recvMetricV4 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v4FlowName).State()) recvMetricV6 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v6FlowName).State()) @@ -223,4 +247,16 @@ func TestMPLSLabelBlockWithISIS(t *testing.T) { otgutils.LogPortMetrics(t, otg, ts.ATETop) verifyTraffic(t, ts.ATE) }) + + // SR counters checks + t.Run("SR counters checks", func(t *testing.T) { + t.Logf("Starting traffic") + otg.StartTraffic(t) + time.Sleep(time.Second * 15) + t.Logf("Stop traffic") + otg.StopTraffic(t) + + t.Logf("Starting SR counters checks") + verifySRCounters(t, ts, ts.ATE) + }) } diff --git a/feature/mpls/sr/otg_tests/srte_egress_node_forwarding/README.md b/feature/mpls/sr/otg_tests/srte_egress_node_forwarding/README.md new file mode 100644 index 00000000000..b444ed98da7 --- /dev/null +++ b/feature/mpls/sr/otg_tests/srte_egress_node_forwarding/README.md @@ -0,0 +1,221 @@ +# SR-1.2: Egress Node Forwarding for MPLS traffic with Explicit Null label + +## Summary + +This is to test the forwarding functionality of MPLS traffic with the Explicit Null label on an Egress node +in SRTE+MPLS enabled network. + +The tests validate that the DUT performs the following actions - + + - DUT is an egress node in SRTE+MPLS network. + - DUT will receive traffic with MPLS Explicit Null label 0 and label 2 for IPv4 and IPv6 destination respectively. + - DUT will pop the MPLS label and perform IPv4 and IPv6 forwarding. + + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + +* Create the following connections: +* DUT has ingress and egress port connected to the ATE. + +```mermaid +graph LR; +A[ATE:Port1] --Ingress--> B[Port1:DUT:Port2];B --Egress--> C[Port2:ATE]; +``` + +* ATE Port 1 hosted prefixes: + + * ATE-Port1 IPv4 address = ATE-P1-Address + * Additional Source Address = IPV4-SRC1 + * Additional Source Address = IPV6-SRC1 + +* ATE Port 2 hosted prefixes: + + * ATE-Port2 IPv4 address = ATE-P2-Address + * Additional destination address = IPV4-DST1 + * Additional destination address = IPV6-DST1 + +* ATE Port 1 generates below flow types: + + * Flow type 1: Ethernet+MPLS+IPv4+Payload + * For the Ethernet Header: + * Source MAC address: Unicast Ethernet MAC + * Destination MAC address: DUT-Port1 mac address + * For MPLS header: + * MPLS label 0 + * EXP set to 0 + * S set to 1 + * TTL set to 64 + * For the IP header: + * Source IP and Destination IP will be IPV4-SRC1 and IPV4-DST1 respectively. + * Protocol will be TCP and UDP and source port (> 1024) and destination port will be 443. + + * Flow type 2: Ethernet+MPLS+IPv6+Payload + * For the Ethernet Header: + * Source MAC address: Unicast Ethernet MAC + * Destination MAC address: DUT-Port1 mac address + * For MPLS header: + * MPLS label 2 + * EXP set to 0 + * S set to 1 + * TTL set to 64 + * For the IPv6 header: + * Source IP and Destination IP will be IPV6-SRC1 and IPV6-DST1 respectively. + * Protocol will be TCP and UDP and source port (> 1024) and destination port will be 443. + +## Procedure + +### Configuration + +* Configure Segment Routing Global Block (srgb) lower-bound: 400000 upper-bound: 465000) +* Enable MPLS forwarding. +* DUT will have a static IPv4 and IPv6 route for IPV4-DST1 / IPV6-DST1 towards ATE Port2. + +### Test + +Verify that: + +* ATE Port1 will send IPv4 and IPv6 traffic. +* DUT will POP MPLS label 0, decrement the TTL from 64 to 63, and perform IPv4 lookup for the destination and forward IPv4 traffic. +* DUT will POP MPLS label 2, decrement the TTL from 64 to 63, and perform IPv6 lookup for the destination and forward IPv6 traffic. +* Validate the ingress and egress counter and ensure that there is no drop in the traffic. + +``` +{ + "openconfig-network-instance:network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT", + "type": "openconfig-network-instance-types:DEFAULT_INSTANCE" + }, + "mpls": { + "global": { + "reserved-label-blocks": { + "reserved-label-block": [ + { + "config": { + "local-id": "srlb", + "lower-bound": 16 + }, + "local-id": "srlb" + }, + { + "config": { + "local-id": "isis-sr", + "lower-bound": 400000, + "upper-bound": 465000 + }, + "local-id": "isis-sr" + } + ] + } + } + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "openconfig-policy-types:ISIS", + "name": "isis", + "config": { + "identifier": "openconfig-policy-types:ISIS", + "name": "isis" + }, + "isis": { + "global": { + "segment-routing": { + "config": { + "enabled": true, + "srgb": "isis-sr", + "srlb": "srlb" + } + } + } + } + } + ] + }, + "segment-routing": { + "srgbs": { + "srgb": [ + { + "config": { + "dataplane-type": "MPLS", + "local-id": "isis-sr", + "mpls-label-blocks": [ + "isis-sr" + ] + }, + "local-id": "isis-sr" + } + ] + }, + "srlbs": { + "srlb": [ + { + "config": { + "dataplane-type": "MPLS", + "local-id": "srlb", + "mpls-label-block": "srlb" + }, + "local-id": "srlb" + } + ] + } + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/state/upper-bound: + /network-instances/network-instance/segment-routing/srgbs/srgb/local-id: + /network-instances/network-instance/protocols/protocol/state/identifier: + /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/state/srgb: + /network-instances/network-instance/segment-routing/srlbs/srlb/state/local-id: + /network-instances/network-instance/segment-routing/srlbs/srlb/state/dataplane-type: + /network-instances/network-instance/state/type: + /network-instances/network-instance/protocols/protocol/identifier: + /network-instances/network-instance/protocols/protocol/state/name: + /network-instances/network-instance/segment-routing/srgbs/srgb/state/dataplane-type: + /network-instances/network-instance/name: + /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/state/srlb: + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/state/lower-bound: + /network-instances/network-instance/protocols/protocol/isis/global/segment-routing/state/enabled: + /network-instances/network-instance/segment-routing/srgbs/srgb/state/mpls-label-blocks: + /network-instances/network-instance/segment-routing/srlbs/srlb/local-id: + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/local-id: + /network-instances/network-instance/state/name: + /network-instances/network-instance/segment-routing/srgbs/srgb/state/local-id: + /network-instances/network-instance/segment-routing/srlbs/srlb/state/mpls-label-block: + /network-instances/network-instance/mpls/global/reserved-label-blocks/reserved-label-block/state/local-id: + + + # Telemetry + /network-instances/network-instance/mpls/signaling-protocols/segment-routing/aggregate-sid-counters/aggregate-sid-counter/state/in-pkts: + /network-instances/network-instance/mpls/signaling-protocols/segment-routing/aggregate-sid-counters/aggregate-sid-counter/state/out-pkts: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` +## Required DUT platform + +* FFF +* MFF diff --git a/feature/mpls/sr/otg_tests/srte_egress_node_forwarding/metadata.textproto b/feature/mpls/sr/otg_tests/srte_egress_node_forwarding/metadata.textproto new file mode 100644 index 00000000000..76500603a5b --- /dev/null +++ b/feature/mpls/sr/otg_tests/srte_egress_node_forwarding/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "387879f4-785f-4f95-9132-33c611f19d2c" +plan_id: "SR-1.2" +description: "Egress Node Forwarding for MPLS traffic with Explicit Null label" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/networkinstance/feature.textproto b/feature/networkinstance/feature.textproto deleted file mode 100644 index 10e9b7da93c..00000000000 --- a/feature/networkinstance/feature.textproto +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "networkinstance" - version: 1 -} - -# Network Instance config -config_path { - path: "/network-instances/network-instance/name" -} -config_path { - path: "/network-instances/network-instance/config/name" -} -telemetry_path { - path: "/network-instances/network-instance/state/name" -} -config_path { - path: "/network-instances/network-instance/config/type" -} -telemetry_path { - path: "/network-instances/network-instance/state/type" -} -config_path { - path: "/network-instances/network-instance/config/type" -} -telemetry_path { - path: "/network-instances/network-instance/state/type" -} -config_path { - path: "/network-instances/network-instance/config/description" -} -telemetry_path { - path: "/network-instances/network-instance/state/description" -} - -# Interfaces -config_path { - path: "/network-instances/network-instance/interfaces/interface/config/id" -} -telemetry_path { - path: "/network-instances/network-instance/interfaces/interface/state/id" -} -config_path { - path: "/network-instances/network-instance/interfaces/interface/id" -} diff --git a/feature/networkinstance/local_aggregates/tests/policy_advertise_aggregate/README.md b/feature/networkinstance/local_aggregates/tests/policy_advertise_aggregate/README.md new file mode 100644 index 00000000000..157f5b0d686 --- /dev/null +++ b/feature/networkinstance/local_aggregates/tests/policy_advertise_aggregate/README.md @@ -0,0 +1,199 @@ +# RT-10.1: Default Route Generation based on 192.0.0.0/8 Presence + +## Testbed Topology + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + +* Connect ATE Port-1 to DUT Port-1 +* Preconditions: + * A BGP speaker DUT is configured. + * BGP peering is established and stable between DUT and at least one neighbor ATE-1. + * A policy to generate default route 0.0.0.0/0 upon receiving a bgp route 192.0.0.0/8 + * Initial routing tables are verified to be free of any 192.0.0.0/8 or 0.0.0.0/0 routes. +* Description of procedure to configure ATE and DUT with pre-requisites making + it possible to cover the intended paths and RPCs. + +### RT-10.1.1 - 192.0.0.0/8 and 0.0.0.0/0 route is present + +* On ATE-1, advertise a bgp route 192.0.0.0/8 towards DUT. +* On DUT, inspect the routing table. +* Expected result: The 192.0.0.0/8 route is visible in DUT's routing table. +* Expected result: A default route (0.0.0.0/0) is now present in DUT. + +### RT-10.1.2 Stop advertising 192.0.0.0/8 and observe 0.0.0.0/0 is removed +* On ATE-1, stop advertising the route 192.0.0.0/8. +* Expected result: The 192.0.0.0/8 route is removed from DUT's routing table. +* Expected result: The default route (0.0.0.0/0) is withdrawn and is no longer present in DUT. + +### Canonical OC + +TODO ([openconfig/public#1368](https://github.com/openconfig/public/issues/1368)): Add advertise-aggregate path to the OpenConfig public data models. +`/routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/advertise-aggregate = `"LOCAL-AGG"` + + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV4_UNICAST", + "apply-policy": { + "config": { + "default-import-policy": "REJECT_ROUTE" + } + }, + "config": { + "afi-safi-name": "IPV4_UNICAST" + } + } + ] + }, + "config": { + "neighbor-address": "192.1.1.1" + }, + "neighbor-address": "192.1.1.1" + }, + { + "afi-safis": { + "afi-safi": [ + { + "afi-safi-name": "IPV6_UNICAST", + "apply-policy": { + "config": { + "default-import-policy": "REJECT_ROUTE" + } + }, + "config": { + "afi-safi-name": "IPV6_UNICAST" + } + } + ] + }, + "config": { + "neighbor-address": "2001:db9::1" + }, + "neighbor-address": "2001:db9::1" + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + }, + { + "config": { + "identifier": "LOCAL_AGGREGATE", + "name": "DEFAULT-AGG" + }, + "identifier": "LOCAL_AGGREGATE", + "local-aggregates": { + "aggregate": [ + { + "config": { + "prefix": "0.0.0.0/0" + }, + "prefix": "0.0.0.0/0" + } + ] + }, + "name": "DEFAULT-AGG" + } + ] + } + } + ] + }, + "routing-policy": { + "defined-sets": { + "prefix-sets": { + "prefix-set": [ + { + "config": { + "mode": "IPV4", + "name": "EBGP-IMPORT-IPV4" + }, + "name": "EBGP-IMPORT-IPV4", + "prefixes": { + "prefix": [ + { + "config": { + "ip-prefix": "192.0.2.0/32", + "masklength-range": "exact" + }, + "ip-prefix": "192.0.2.0/32", + "masklength-range": "exact" + } + ] + } + } + ] + } + }, + "policy-definitions": { + "policy-definition": [ + { + "config": { + "name": "EBGP-IMPORT-IPV4" + }, + "name": "EBGP-IMPORT-IPV4", + "statements": { + "statement": [ + { + "actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + }, + "conditions": { + "match-prefix-set": { + "config": { + "match-set-options": "INVERT", + "prefix-set": "EBGP-IMPORT-IPV4" + } + } + }, + "config": { + "name": "10" + }, + "name": "10" + } + ] + } + } + ] + } + } +} + +``` + +### OpenConfig Path and RPC Coverage +```yaml +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` diff --git a/feature/networkinstance/local_aggregates/tests/policy_advertise_aggregate/metadata.textproto b/feature/networkinstance/local_aggregates/tests/policy_advertise_aggregate/metadata.textproto new file mode 100644 index 00000000000..60555d164bd --- /dev/null +++ b/feature/networkinstance/local_aggregates/tests/policy_advertise_aggregate/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "7b2f4621-996a-4d69-b822-433f3193dca4" +plan_id: "RT-10.1" +description: "Default Route Generation based on 192.0.0.0/8 Presence" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/p4rt/README.md b/feature/p4rt/README.md index 3acc900e9fc..e85f3a7ac04 100644 --- a/feature/p4rt/README.md +++ b/feature/p4rt/README.md @@ -8,7 +8,7 @@ This document specifies the requirements for p4rt test implementation. following Google compatible format: 1. WBB P4 Protobuf file: - https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/p4rt/wbb.p4info.pb.txt + https://github.com/openconfig/featureprofiles/blob/main/feature/p4rt/data/wbb.p4info.pb.txt 3. Tests should create new P4RT clients using the `p4rt_client.NewP4RTClient()` function, which sets up the `StreamTermErr` channel required to check errors diff --git a/feature/p4rt/wbb.p4info.pb.txt b/feature/p4rt/data/wbb.p4info.20230428.pb.txt similarity index 100% rename from feature/p4rt/wbb.p4info.pb.txt rename to feature/p4rt/data/wbb.p4info.20230428.pb.txt diff --git a/feature/p4rt/data/wbb.p4info.20240710.pb.txt b/feature/p4rt/data/wbb.p4info.20240710.pb.txt new file mode 100644 index 00000000000..13628880713 --- /dev/null +++ b/feature/p4rt/data/wbb.p4info.20240710.pb.txt @@ -0,0 +1,169 @@ +# proto-file: third_party/p4lang_p4runtime/proto/p4/config/v1/p4info.proto +# proto-message: p4.config.v1.P4Info + +pkg_info { + name: "wbb.p4" + version: "0.0.0" + arch: "v1model" + organization: "Google" +} +tables { + preamble { + id: 33554691 + name: "ingress.acl_wbb_ingress.acl_wbb_ingress_table" + alias: "acl_wbb_ingress_table" + annotations: "@p4runtime_role(\"sdn_controller\")" + annotations: "@sai_acl(INGRESS)" + annotations: "@entry_restriction(\"\n // WBB only allows for very specific table entries:\n\n // Traceroute (6 entries)\n (\n // IPv4 or IPv6\n ((is_ipv4 == 1 && is_ipv6::mask == 0) ||\n (is_ipv4::mask == 0 && is_ipv6 == 1)) &&\n // TTL 0, 1, and 2\n (ttl == 0 || ttl == 1 || ttl == 2) &&\n ether_type::mask == 0\n ) ||\n // LLDP\n (\n ether_type == 0x88cc &&\n is_ipv4::mask == 0 && is_ipv6::mask == 0 && ttl::mask == 0\n ) ||\n // ND\n (\n ether_type == 0x6007;\n is_ipv4::mask == 0;\n is_ipv6::mask == 0;\n ttl::mask == 0\n )\n \")" + } + match_fields { + id: 1 + name: "is_ipv4" + annotations: "@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE / IPV4ANY)" + bitwidth: 1 + match_type: OPTIONAL + } + match_fields { + id: 2 + name: "is_ipv6" + annotations: "@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ACL_IP_TYPE / IPV6ANY)" + bitwidth: 1 + match_type: OPTIONAL + } + match_fields { + id: 3 + name: "ether_type" + annotations: "@sai_field(SAI_ACL_TABLE_ATTR_FIELD_ETHER_TYPE)" + bitwidth: 16 + match_type: TERNARY + } + match_fields { + id: 4 + name: "ttl" + annotations: "@sai_field(SAI_ACL_TABLE_ATTR_FIELD_TTL)" + bitwidth: 8 + match_type: TERNARY + } + action_refs { + id: 16777479 + annotations: "@proto_id(1)" + } + action_refs { + id: 16777480 + annotations: "@proto_id(2)" + } + action_refs { + id: 21257015 + annotations: "@defaultonly" + scope: DEFAULT_ONLY + } + const_default_action_id: 21257015 + direct_resource_ids: 318767363 + direct_resource_ids: 352321793 + size: 8 +} +actions { + preamble { + id: 21257015 + name: "NoAction" + alias: "NoAction" + annotations: "@noWarn(\"unused\")" + } +} +actions { + preamble { + id: 16777479 + name: "ingress.acl_wbb_ingress.acl_wbb_ingress_copy" + alias: "acl_wbb_ingress_copy" + annotations: "@sai_action(SAI_PACKET_ACTION_COPY)" + } +} +actions { + preamble { + id: 16777480 + name: "ingress.acl_wbb_ingress.acl_wbb_ingress_trap" + alias: "acl_wbb_ingress_trap" + annotations: "@sai_action(SAI_PACKET_ACTION_TRAP)" + } +} +direct_counters { + preamble { + id: 318767363 + name: "ingress.acl_wbb_ingress.acl_wbb_ingress_counter" + alias: "acl_wbb_ingress_counter" + } + spec { + unit: BOTH + } + direct_table_id: 33554691 +} +direct_meters { + preamble { + id: 352321793 + name: "ingress.acl_wbb_ingress.acl_wbb_ingress_meter" + alias: "acl_wbb_ingress_meter" + } + spec { + unit: BYTES + } + direct_table_id: 33554691 +} +controller_packet_metadata { + preamble { + id: 81826293 + name: "packet_in" + alias: "packet_in" + annotations: "@controller_header(\"packet_in\")" + } + metadata { + id: 1 + name: "ingress_port" + type_name { + name: "port_id_t" + } + } + metadata { + id: 2 + name: "target_egress_port" + type_name { + name: "port_id_t" + } + } +} +controller_packet_metadata { + preamble { + id: 76689799 + name: "packet_out" + alias: "packet_out" + annotations: "@controller_header(\"packet_out\")" + } + metadata { + id: 1 + name: "egress_port" + type_name { + name: "port_id_t" + } + } + metadata { + id: 2 + name: "submit_to_ingress" + bitwidth: 1 + } + metadata { + id: 3 + name: "unused_pad" + annotations: "@padding" + bitwidth: 6 + } +} +type_info { + new_types { + key: "port_id_t" + value { + translated_type { + sdn_string { + } + } + } + } +} diff --git a/feature/p4rt/data/wbb.p4info.pb.txt b/feature/p4rt/data/wbb.p4info.pb.txt new file mode 120000 index 00000000000..a455422c1cc --- /dev/null +++ b/feature/p4rt/data/wbb.p4info.pb.txt @@ -0,0 +1 @@ +wbb.p4info.20230428.pb.txt \ No newline at end of file diff --git a/feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go b/feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go index bd246fc9d1d..1225a0e1de0 100644 --- a/feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go +++ b/feature/p4rt/otg_tests/base_p4rt/base_p4rt_test.go @@ -57,7 +57,7 @@ const ( var ( // Path to the p4Info file for sending it with SetFwdPipelineConfig - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName1 = "p4rt1" streamName2 = "p4rt2" @@ -350,13 +350,6 @@ func TestP4rtConnect(t *testing.T) { EtherTypeMask: 0xFFFF, Priority: 1, }, - { - Type: p4_v1.Update_INSERT, - IsIpv4: 0x1, - TTL: 0x0, - TTLMask: 0xFF, - Priority: 1, - }, { Type: p4_v1.Update_INSERT, IsIpv4: 0x1, @@ -364,20 +357,6 @@ func TestP4rtConnect(t *testing.T) { TTLMask: 0xFF, Priority: 1, }, - { - Type: p4_v1.Update_INSERT, - IsIpv4: 0x1, - TTL: 0x2, - TTLMask: 0xFF, - Priority: 1, - }, - { - Type: p4_v1.Update_INSERT, - IsIpv6: 0x1, - TTL: 0x0, - TTLMask: 0xFF, - Priority: 1, - }, { Type: p4_v1.Update_INSERT, IsIpv6: 0x1, @@ -385,13 +364,6 @@ func TestP4rtConnect(t *testing.T) { TTLMask: 0xFF, Priority: 1, }, - { - Type: p4_v1.Update_INSERT, - IsIpv6: 0x1, - TTL: 0x2, - TTLMask: 0xFF, - Priority: 1, - }, }), Atomicity: p4_v1.WriteRequest_CONTINUE_ON_ERROR, }) @@ -468,50 +440,6 @@ func TestP4rtConnect(t *testing.T) { t.Errorf("TableEntry for traceroute v4 & ttl=1: %s", err) } - // Construct expected table for traceroute v4 & TTL = 0 to match with received table entry - expected_update = p4rtutils.ACLWbbIngressTableEntryGet([]*p4rtutils.ACLWbbIngressTableEntryInfo{ - { - Type: p4_v1.Update_INSERT, - IsIpv4: 0x1, - TTL: 0x0, - TTLMask: 0xFF, - Priority: 1, - }, - }) - expected_entity = expected_update[0] - if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { - t.Errorf("TableEntry for traceroute v4 & ttl=0: %s", err) - } - // Construct expected table for traceroute v4 & TTL = 2 to match with received table entry - expected_update = p4rtutils.ACLWbbIngressTableEntryGet([]*p4rtutils.ACLWbbIngressTableEntryInfo{ - { - Type: p4_v1.Update_INSERT, - IsIpv4: 0x1, - TTL: 0x2, - TTLMask: 0xFF, - Priority: 1, - }, - }) - expected_entity = expected_update[0] - if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { - t.Errorf("TableEntry for traceroute v4 & ttl=2: %s", err) - } - - // Construct expected table for traceroute v6 & TTL = 0 to match with received table entry - expected_update = p4rtutils.ACLWbbIngressTableEntryGet([]*p4rtutils.ACLWbbIngressTableEntryInfo{ - { - Type: p4_v1.Update_INSERT, - IsIpv6: 0x1, - TTL: 0x0, - TTLMask: 0xFF, - Priority: 1, - }, - }) - expected_entity = expected_update[0] - if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { - t.Errorf("TableEntry for traceroute v6 & ttl=0: %s", err) - } - // Construct expected table for traceroute v6 & TTL = 1 to match with received table entry expected_update = p4rtutils.ACLWbbIngressTableEntryGet([]*p4rtutils.ACLWbbIngressTableEntryInfo{ { @@ -526,20 +454,5 @@ func TestP4rtConnect(t *testing.T) { if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { t.Errorf("TableEntry for traceroute v6 & ttl=1: %s", err) } - // Construct expected table for traceroute v6 & TTL = 2 to match with received table entry - expected_update = p4rtutils.ACLWbbIngressTableEntryGet([]*p4rtutils.ACLWbbIngressTableEntryInfo{ - { - Type: p4_v1.Update_INSERT, - IsIpv6: 0x1, - TTL: 0x2, - TTLMask: 0xFF, - Priority: 1, - }, - }) - expected_entity = expected_update[0] - if err := verifyReadReceiveMatch(t, expected_entity, readResp); err != nil { - t.Errorf("TableEntry for traceroute v6 & ttl=2: %s", err) - } - } } diff --git a/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go b/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go index 3ab3685a664..0a6b2d9b971 100644 --- a/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetin_test/google_discovery_protocol_packetin_test.go @@ -46,7 +46,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") gdpSrcMAC = flag.String("gdp_src_MAC", "00:01:00:02:00:03", "source MAC address for PacketIn") streamName = "p4rt" gdpDstMAC = "00:0a:da:f0:f0:f0" diff --git a/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/google_discovery_protocol_packetout_lag_test.go b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/google_discovery_protocol_packetout_lag_test.go index a7af3d61fe4..9ffc9201da9 100644 --- a/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/google_discovery_protocol_packetout_lag_test.go +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_lag_test/google_discovery_protocol_packetout_lag_test.go @@ -47,7 +47,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName = "p4rt" gdpInLayers layers.EthernetType = 0x6007 deviceID = uint64(1) diff --git a/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go index a407ef21f0d..2229534e66e 100644 --- a/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go +++ b/feature/p4rt/otg_tests/google_discovery_protocol_packetout_test/google_discovery_protocol_packetout_test.go @@ -46,7 +46,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName = "p4rt" gdpInLayers layers.EthernetType = 0x6007 deviceID = uint64(1) diff --git a/feature/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go b/feature/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go index e95e9a63e83..79ee6301d2f 100644 --- a/feature/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go +++ b/feature/p4rt/otg_tests/lldp_packetin_test/lldp_packetin_test.go @@ -45,7 +45,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") lldpSrcMAC = flag.String("lldp_src_MAC", "00:01:00:02:00:03", "source MAC address for PacketIn") streamName = "p4rt" lldpMAC = "01:80:c2:00:00:0e" diff --git a/feature/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go b/feature/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go index 46c5657156c..47fd53f5e7f 100644 --- a/feature/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go +++ b/feature/p4rt/otg_tests/lldp_packetout_test/lldp_packetout_test.go @@ -47,7 +47,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName = "p4rt" lldpInLayers layers.EthernetType = 0x88cc deviceID = *ygot.Uint64(1) diff --git a/feature/p4rt/otg_tests/performance_test/performance_test.go b/feature/p4rt/otg_tests/performance_test/performance_test.go index a28ec87f1e1..b8027252f0b 100644 --- a/feature/p4rt/otg_tests/performance_test/performance_test.go +++ b/feature/p4rt/otg_tests/performance_test/performance_test.go @@ -61,7 +61,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName = "p4rt" pktInSrcMAC = "00:02:00:03:00:04" pktOutSrcMAC = "00:01:00:02:00:03" diff --git a/feature/p4rt/otg_tests/traceroute_packetin_test/README.md b/feature/p4rt/otg_tests/traceroute_packetin_test/README.md index 0aee47016e4..a37a04b76db 100644 --- a/feature/p4rt/otg_tests/traceroute_packetin_test/README.md +++ b/feature/p4rt/otg_tests/traceroute_packetin_test/README.md @@ -4,6 +4,7 @@ ## Summary Verify that Traceroute packets are punted with correct metadata. +Verify that P4RT configurations persist after client connectivity drops. After client re-connection without programming the P4RT config verify traceroute packets are still punted. ## Procedure @@ -15,6 +16,173 @@ Verify that Traceroute packets are punted with correct metadata. * Send IPv4 packets from the ATE with TTL=1 and verify that packets with TTL=1 are received by the client. * Send IPv6 packets from the ATE with HopLimit=1 and verify that packets with HopLimit=1 are received by the client. * Verify that the packets have both ingress_singleton_port and egress_singleton_port metadata set. +* Disconnect clients +* Reconnect clients without setting P4RT config using SetForwardingPipelineConfig +* Start traffic and verify packets are punted as before + +## Canonical OC +```json +{ + "components": { + "component": [ + { + "config": { + "name": "P4RT_NODE" + }, + "integrated-circuit": { + "config": { + "node-id": "1" + } + }, + "name": "P4RT_NODE" + } + ] + }, + "interfaces": { + "interface": [ + { + "config": { + "description": "dutPort1", + "enabled": true, + "name": "port1", + "type": "ethernetCsmacd" + }, + "name": "port1", + "subinterfaces": { + "subinterface": [ + { + "config": { + "enabled": true, + "index": 0 + }, + "index": 0, + "ipv4": { + "addresses": { + "address": [ + { + "config": { + "ip": "192.0.2.1", + "prefix-length": 30 + }, + "ip": "192.0.2.1" + } + ] + }, + "config": { + "enabled": true + } + }, + "ipv6": { + "addresses": { + "address": [ + { + "config": { + "ip": "2001:db8::1", + "prefix-length": 126 + }, + "ip": "2001:db8::1" + } + ] + }, + "config": { + "enabled": true + } + } + } + ] + } + }, + { + "config": { + "description": "dutPort2", + "enabled": true, + "name": "port2", + "type": "ethernetCsmacd" + }, + "name": "port2", + "subinterfaces": { + "subinterface": [ + { + "config": { + "enabled": true, + "index": 0 + }, + "index": 0, + "ipv4": { + "addresses": { + "address": [ + { + "config": { + "ip": "192.0.2.5", + "prefix-length": 30 + }, + "ip": "192.0.2.5" + } + ] + }, + "config": { + "enabled": true + } + }, + "ipv6": { + "addresses": { + "address": [ + { + "config": { + "ip": "2001:db8::5", + "prefix-length": 126 + }, + "ip": "2001:db8::5" + } + ] + }, + "config": { + "enabled": true + } + } + } + ] + } + } + ] + }, + "lldp": { + "config": { + "enabled": false + } + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "interfaces": { + "interface": [ + { + "config": { + "id": "port1", + "interface": "port1", + "subinterface": 0 + }, + "id": "port1" + }, + { + "config": { + "id": "port2", + "interface": "port2", + "subinterface": 0 + }, + "id": "port2" + } + ] + }, + "name": "DEFAULT" + } + ] + } +} +``` ## OpenConfig Path and RPC Coverage ```yaml diff --git a/feature/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go b/feature/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go index 7dd015b3db4..351bdfb4fe9 100644 --- a/feature/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go +++ b/feature/p4rt/otg_tests/traceroute_packetin_test/packetin_test.go @@ -59,10 +59,10 @@ type testArgs struct { } // programmTableEntry programs or deletes p4rt table entry based on delete flag. -func programmTableEntry(client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool, isIPv4 bool) error { +func programmTableEntry(client *p4rt_client.P4RTClient, packetIO PacketIO, delete bool, isIPv4 bool, electionID uint64) error { err := client.Write(&p4_v1.WriteRequest{ DeviceId: deviceID, - ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionId}, + ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionID}, Updates: p4rtutils.ACLWbbIngressTableEntryGet( packetIO.GetTableEntry(delete, isIPv4), ), @@ -133,28 +133,12 @@ func testTraffic(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, flow 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) { +// startTraficAndTestPacketIn sends traffic related to Traceroutethen validates packetin message metadata and payload. +func startTraficAndTestPacketIn(ctx context.Context, t *testing.T, args *testArgs, isIPv4 bool) { + t.Helper() leader := args.leader follower := args.follower - 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) - } - // Send Traceroute traffic from ATE srcEndPoint := ateInterface(t, args.top, "port1") llAddress, found := gnmi.Watch(t, args.ate.OTG(), gnmi.OTG().Interface(atePort1.Name+".Eth").Ipv4Neighbor(dutPort1.IPv4).LinkLayerAddress().State(), time.Minute, func(val *ygnmi.Value[string]) bool { @@ -252,6 +236,7 @@ func testPacketIn(ctx context.Context, t *testing.T, args *testArgs, isIPv4 bool } func ateInterface(t *testing.T, topo gosnappi.Config, portID string) gosnappi.Port { + t.Helper() for _, p := range topo.Ports().Items() { if p.Name() == portID { return p diff --git a/feature/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go b/feature/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go index bbc0d218f9b..4d81a3c0ee8 100644 --- a/feature/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go +++ b/feature/p4rt/otg_tests/traceroute_packetin_test/traceroute_packetin_test.go @@ -18,7 +18,6 @@ package traceroute_packetin_test import ( "context" - "errors" "fmt" "testing" @@ -39,12 +38,11 @@ import ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/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) METADATA_INGRESS_PORT = uint32(1) METADATA_EGRESS_PORT = uint32(2) TTL1 = uint8(1) @@ -171,69 +169,93 @@ func configureDeviceID(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice 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 { +// creates p4rt stream and sends client arbitration message for a single client. +func setupP4RTClient(ctx context.Context, t *testing.T, client *p4rt_client.P4RTClient, electionID uint64) error { + t.Helper() // Setup p4rt-client stream parameters streamParameter := p4rt_client.P4RTStreamParameters{ Name: streamName, DeviceId: deviceID, ElectionIdH: uint64(0), - ElectionIdL: electionId, + ElectionIdL: electionID, + } + streamCreateErr := client.StreamChannelCreate(&streamParameter) + if streamCreateErr != nil { + return fmt.Errorf("could not create P4RT client stream: %v", streamCreateErr) + } + sendMsgErr := client.StreamChannelSendMsg(&streamName, &p4_v1.StreamMessageRequest{ + Update: &p4_v1.StreamMessageRequest_Arbitration{ + Arbitration: &p4_v1.MasterArbitrationUpdate{ + DeviceId: streamParameter.DeviceId, + ElectionId: &p4_v1.Uint128{ + High: streamParameter.ElectionIdH, + Low: streamParameter.ElectionIdL, + }, + }, + }, + }) + if sendMsgErr != nil { + return fmt.Errorf("could not send ClientArbitration message: %v", sendMsgErr) } + _, _, arbErr := client.StreamChannelGetArbitrationResp(&streamName, 1) + if arbErr != nil { + if err := p4rtutils.StreamTermErr(client.StreamTermErr); err != nil { + return fmt.Errorf("stream term error while getting arbitration response: %v", err) + } + return fmt.Errorf("errors seen in ClientArbitration response: %v", arbErr) + } + return nil +} +// setupP4RTLeaderAndFollower sends client arbitration message for both leader and follower clients, +// then sends setforwordingpipelineconfig with leader client. +func setupP4RTLeaderAndFollower(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, setForwardingPipelineConfig bool, electionID uint64) (*p4rt_client.P4RTClient, *p4rt_client.P4RTClient, error) { + t.Helper() + leader := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := leader.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + return nil, nil, fmt.Errorf("could not initialize p4rt client: %v", err) + } + follower := p4rt_client.NewP4RTClient(&p4rt_client.P4RTClientParameters{}) + if err := follower.P4rtClientSet(dut.RawAPIs().P4RT(t)); err != nil { + return nil, nil, fmt.Errorf("could not initialize p4rt client: %v", err) + } // Send ClientArbitration message on both p4rt leader and follower clients. - clients := []*p4rt_client.P4RTClient{args.leader, args.follower} + clients := []*p4rt_client.P4RTClient{leader, follower} for index, client := range clients { if client != nil { - client.StreamChannelCreate(&streamParameter) - if err := client.StreamChannelSendMsg(&streamName, &p4_v1.StreamMessageRequest{ - Update: &p4_v1.StreamMessageRequest_Arbitration{ - Arbitration: &p4_v1.MasterArbitrationUpdate{ - DeviceId: streamParameter.DeviceId, - ElectionId: &p4_v1.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) + if err := setupP4RTClient(ctx, t, client, electionID-uint64(index)); err != nil { + return nil, nil, fmt.Errorf("could not setup p4rt client: %v", err) } } } - // 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(&p4_v1.SetForwardingPipelineConfigRequest{ - DeviceId: deviceID, - ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionId}, - Action: p4_v1.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, - Config: &p4_v1.ForwardingPipelineConfig{ - P4Info: p4Info, - Cookie: &p4_v1.ForwardingPipelineConfig_Cookie{ - Cookie: 159, + if setForwardingPipelineConfig { + // Load p4info file. + p4Info, err := utils.P4InfoLoad(p4InfoFile) + if err != nil { + return nil, nil, fmt.Errorf("errors seen when loading p4info file: %v", err) + } + // Send SetForwardingPipelineConfig for p4rt leader client. + if err := leader.SetForwardingPipelineConfig(&p4_v1.SetForwardingPipelineConfigRequest{ + DeviceId: deviceID, + ElectionId: &p4_v1.Uint128{High: uint64(0), Low: electionID}, + Action: p4_v1.SetForwardingPipelineConfigRequest_VERIFY_AND_COMMIT, + Config: &p4_v1.ForwardingPipelineConfig{ + P4Info: p4Info, + Cookie: &p4_v1.ForwardingPipelineConfig_Cookie{ + Cookie: 159, + }, }, - }, - }); err != nil { - return errors.New("Errors seen when sending SetForwardingPipelineConfig.") + }); err != nil { + return nil, nil, fmt.Errorf("errors seen when sending SetForwardingPipelineConfig: %v", err) + } } - return nil + return leader, follower, nil } -// getTracerouteParameter returns Traceroute related parameters for testPacketIn testcase. +// getTracerouteParameter returns Traceroute related parameters for startTraficAndTestPacketIn testcase. func getTracerouteParameter(t *testing.T) PacketIO { + t.Helper() return &TraceroutePacketIO{ PacketIOPacket: PacketIOPacket{ TTL: &TTL1, @@ -244,9 +266,113 @@ func getTracerouteParameter(t *testing.T) PacketIO { } } +func testPacketInForLeader(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, top gosnappi.Config, electionID uint64) { + t.Helper() + leader, follower, err := setupP4RTLeaderAndFollower(ctx, t, dut, true /*setForwardingPipelineConfig*/, electionID) + if err != nil { + t.Fatalf("Could not setup p4rt client: %v", err) + } + defer func() { + leader.StreamChannelDestroy(&streamName) + leader.ServerDisconnect() + follower.StreamChannelDestroy(&streamName) + follower.ServerDisconnect() + }() + args := &testArgs{ + ctx: ctx, + leader: leader, + follower: follower, + dut: dut, + ate: ate, + top: top, + packetIO: getTracerouteParameter(t), + } + packetInTests := []struct { + desc string + isIPv4 bool + }{{ + desc: "Test PacketIn for IPv4", + isIPv4: true, + }, { + desc: "Test PacketIn for IPv6", + isIPv4: false, + }} + for _, test := range packetInTests { + t.Run(test.desc, func(t *testing.T) { + if err := programmTableEntry(leader, args.packetIO, false /*delete*/, test.isIPv4, electionID); err != nil { + t.Fatalf("there is error when programming entry") + } + defer programmTableEntry(leader, args.packetIO, true /*delete*/, test.isIPv4, electionID) + startTraficAndTestPacketIn(ctx, t, args, test.isIPv4) + }) + } +} + +func testPacketInAfterClientReconnection(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, top gosnappi.Config, electionID uint64) { + t.Helper() + leader, follower, err := setupP4RTLeaderAndFollower(ctx, t, dut, true /*setForwardingPipelineConfig*/, electionID) + if err != nil { + t.Fatalf("Could not setup p4rt client: %v", err) + } + args := &testArgs{ + ctx: ctx, + leader: leader, + follower: follower, + dut: dut, + ate: ate, + top: top, + packetIO: getTracerouteParameter(t), + } + packetInTests := []struct { + desc string + isIPv4 bool + }{{ + desc: "Test PacketIn for IPv4", + isIPv4: true, + }, { + desc: "Test PacketIn for IPv6", + isIPv4: false, + }} + for _, test := range packetInTests { + t.Run(test.desc, func(t *testing.T) { + if err := programmTableEntry(leader, args.packetIO, false /*delete*/, test.isIPv4, electionID); err != nil { + t.Fatalf("there is error when programming entry") + } + startTraficAndTestPacketIn(ctx, t, args, test.isIPv4) + // Observation: defer programmTableEntry(leader, args.packetIO, true /*delete*/, test.isIPv4, electionID) + // is not included so we can test if the policy remains in the table after the client connection drops and reconnects. + }) + } + + leader.StreamChannelDestroy(&streamName) + leader.ServerDisconnect() + follower.StreamChannelDestroy(&streamName) + follower.ServerDisconnect() + + newLeaderElectionID := electionID + 1 + newLeader, newFollower, err := setupP4RTLeaderAndFollower(ctx, t, dut, false /*setForwardingPipelineConfig*/, newLeaderElectionID) + if err != nil { + t.Fatalf("Could not setup new p4rt client: %v", err) + } + defer func() { + newLeader.StreamChannelDestroy(&streamName) + newLeader.ServerDisconnect() + newFollower.StreamChannelDestroy(&streamName) + newFollower.ServerDisconnect() + }() + args.leader = newLeader + args.follower = newFollower + for _, test := range packetInTests { + t.Run(test.desc, func(t *testing.T) { + defer programmTableEntry(args.leader, args.packetIO, true /*delete*/, test.isIPv4, newLeaderElectionID) + startTraficAndTestPacketIn(ctx, t, args, test.isIPv4) + }) + } +} + func TestPacketIn(t *testing.T) { dut := ondatra.DUT(t, "dut") - ctx := context.Background() + ctx := t.Context() // Configure the DUT configureDUT(t, dut) @@ -260,32 +386,14 @@ func TestPacketIn(t *testing.T) { ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) - 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, - leader: leader, - follower: 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) - testPacketIn(ctx, t, args, true) // testPacketin for Ipv4 - testPacketIn(ctx, t, args, false) // testPacketin for Ipv6 + t.Run("Create P4RT clients, start traffic, and validate packetins sent to leader", func(t *testing.T) { + electionID := uint64(100) + testPacketInForLeader(t.Context(), t, dut, ate, top, electionID) + }) + t.Run("Create P4RT client, disconnect, start traffic, and validate packetins sent", func(t *testing.T) { + electionID := uint64(200) + testPacketInAfterClientReconnection(t.Context(), t, dut, ate, top, electionID) + }) } type TraceroutePacketIO struct { @@ -307,14 +415,7 @@ func (traceroute *TraceroutePacketIO) GetTableEntry(delete bool, IsIpv4 bool) [] TTL: 0x1, TTLMask: 0xFF, Priority: 1, - }, - { - Type: actionType, - IsIpv4: 0x1, - TTL: 0x0, - TTLMask: 0xFF, - Priority: 1, - }} + }} } else { actionType := p4_v1.Update_INSERT if delete { @@ -326,14 +427,7 @@ func (traceroute *TraceroutePacketIO) GetTableEntry(delete bool, IsIpv4 bool) [] TTL: 0x1, TTLMask: 0xFF, Priority: 1, - }, - { - Type: actionType, - IsIpv6: 0x1, - TTL: 0x0, - TTLMask: 0xFF, - Priority: 1, - }} + }} } } diff --git a/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go index 506bdcbc534..d23cbd90476 100644 --- a/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go +++ b/feature/p4rt/otg_tests/traceroute_packetin_with_vrf_selection_test/traceroute_packetin_with_vrf_selection_test.go @@ -110,7 +110,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName = "p4rt" tracerouteSrcMAC = "00:01:00:02:00:03" deviceID = uint64(1) diff --git a/feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go b/feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go index 74b7b98b949..3725cfbde53 100644 --- a/feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go +++ b/feature/p4rt/otg_tests/traceroute_packetout_test/traceroute_packetout_test.go @@ -53,7 +53,7 @@ const ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") streamName = "p4rt" ) diff --git a/feature/p4rt/tests/metadata_validation_test/README.md b/feature/p4rt/tests/metadata_validation_test/README.md index 8a311ea7d05..57e1a4ae9e3 100644 --- a/feature/p4rt/tests/metadata_validation_test/README.md +++ b/feature/p4rt/tests/metadata_validation_test/README.md @@ -9,7 +9,7 @@ Validate the P4RT server handles Metadata set in Table Entry correctly. * Enable P4RT on a single FAP by configuring an ID on the device and one or more interfaces. * Instantiate a `primary` P4RT client and execute Client Arbitration and Set - the Forwarding Pipeline using the `wbb.p4info.pb.txt` file. + the Forwarding Pipeline using the `data/wbb.p4info.pb.txt` file. * Write a `TableEntry` with `Metadata` field set and validate correct `Metadata` is retrieved in `ReadRequest` for the below scenarios: * Using Update Type as `INSERT`, Write a TableEntry with the `Metadata` diff --git a/feature/p4rt/tests/metadata_validation_test/metadata_validation_test.go b/feature/p4rt/tests/metadata_validation_test/metadata_validation_test.go index b41d965e1b9..9ecb831e519 100644 --- a/feature/p4rt/tests/metadata_validation_test/metadata_validation_test.go +++ b/feature/p4rt/tests/metadata_validation_test/metadata_validation_test.go @@ -20,7 +20,7 @@ import ( ) var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") ) var ( diff --git a/feature/p4rt/tests/p4rt_election/metadata.textproto b/feature/p4rt/tests/p4rt_election/metadata.textproto index f1368cfb1ed..2db602f1e3b 100644 --- a/feature/p4rt/tests/p4rt_election/metadata.textproto +++ b/feature/p4rt/tests/p4rt_election/metadata.textproto @@ -4,4 +4,13 @@ uuid: "73bf3d88-e5c1-4d30-9fae-f40b8363044f" plan_id: "P4RT-2.1" description: "P4RT Election" -testbed: TESTBED_DUT_ATE_2LINKS +testbed: TESTBED_DUT + +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + p4rt_unsetelectionid_primary_allowed: true + } +} diff --git a/feature/p4rt/tests/p4rt_election/p4rt_election_test.go b/feature/p4rt/tests/p4rt_election/p4rt_election_test.go index 306622ed9b0..9f182e6f9ae 100644 --- a/feature/p4rt/tests/p4rt_election/p4rt_election_test.go +++ b/feature/p4rt/tests/p4rt_election/p4rt_election_test.go @@ -25,7 +25,7 @@ import ( // Flag variable definitions var ( - p4InfoFile = flag.String("p4info_file_location", "../../wbb.p4info.pb.txt", "Path to the p4info file.") + p4InfoFile = flag.String("p4info_file_location", "../../data/wbb.p4info.pb.txt", "Path to the p4info file.") ) // Variable definitions @@ -345,6 +345,7 @@ func TestUnsetElectionid(t *testing.T) { wantStatus: codes.NotFound, }, } + if deviations.P4rtUnsetElectionIDPrimaryAllowed(dut) { // For P4 Runtime server implementations that allow unset election id update the // expected status to OK for primary and INVALID_ARGUMENT for the secondary @@ -353,6 +354,7 @@ func TestUnsetElectionid(t *testing.T) { clients[0].wantStatus = codes.OK clients[1].wantStatus = codes.InvalidArgument } + // Connect 2 clients to same deviceID with unset electionId. for _, test := range clients { t.Run(test.desc, func(t *testing.T) { @@ -464,12 +466,7 @@ func TestPrimaryReconnect(t *testing.T) { wantStatus: codes.OK, }, } - if deviations.P4rtBackupArbitrationResponseCode(dut) { - // Change the expected status code to ALREADY_EXISTS for deviant implementations - // that send ALREADY_EXISTS instead of NOT_FOUND to secondary clients when there - // is no primary - testCases[1].wantStatus = codes.AlreadyExists - } + for _, test := range testCases { t.Run(test.desc, func(t *testing.T) { resp, terminated, err := streamP4RTArb(&test) @@ -723,12 +720,6 @@ func TestArbitrationUpdate(t *testing.T) { // After updating electionID, statusCode also changes // to secondary without a primary test.wantStatus = codes.NotFound - if deviations.P4rtBackupArbitrationResponseCode(dut) { - // Change the expected status code to ALREADY_EXISTS for deviant implementations - // that send ALREADY_EXISTS instead of NOT_FOUND to secondary clients when there - // is no primary. - test.wantStatus = codes.AlreadyExists - } resp, terminated, err = streamP4RTArb(&test) if err != nil { diff --git a/feature/platform/controllercard/feature.textproto b/feature/platform/controllercard/feature.textproto deleted file mode 100644 index d8b1bda06cb..00000000000 --- a/feature/platform/controllercard/feature.textproto +++ /dev/null @@ -1,72 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "platform_controllercard" - version: 1 -} - -# Paths -config_path { - path: "/components/component/controller-card/config/power-admin-state" -} -telemetry_path { - path: "/components/component/controller-card/state/power-admin-state" -} -telemetry_path { - path: "/components/component/state/empty" -} -telemetry_path { - path: "/components/component/state/location" -} -telemetry_path { - path: "/components/component/state/oper-status" -} -telemetry_path { - path: "/components/component/state/switchover-ready" -} -telemetry_path { - path: "/components/component/state/redundant-role" -} -telemetry_path { - path: "/components/component/state/last-switchover-time" -} -telemetry_path { - path: "/components/component/state/last-switchover-reason/trigger" -} -telemetry_path { - path: "/components/component/state/last-switchover-reason/details" -} -telemetry_path { - path: "/components/component/state/last-reboot-time" -} -telemetry_path { - path: "/components/component/state/last-reboot-reason" -} -telemetry_path { - path: "/components/component/state/description" -} -telemetry_path { - path: "/components/component/state/hardware-version" -} -telemetry_path { - path: "/components/component/state/id" -} -telemetry_path { - path: "/components/component/state/mfg-name" -} -telemetry_path { - path: "/components/component/state/name" -} -telemetry_path { - path: "/components/component/state/parent" -} -telemetry_path { - path: "/components/component/state/part-no" -} -telemetry_path { - path: "/components/component/state/serial-no" -} -telemetry_path { - path: "/components/component/state/type" -} diff --git a/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/SetRequest_GetRequest_controller_card_switchover_test.go b/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/SetRequest_GetRequest_controller_card_switchover_test.go index 35a4e51daac..4d9e403a670 100644 --- a/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/SetRequest_GetRequest_controller_card_switchover_test.go +++ b/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/SetRequest_GetRequest_controller_card_switchover_test.go @@ -283,8 +283,8 @@ func verifyConfiguredElements(t *testing.T, dut *ondatra.DUTDevice, config *gpb. t.Fatalf("Could not unmarshal config: %v", err) } numInterfaces := len(root.Interface) - if numInterfaces != params.NumLAGInterfaces+numPorts { - t.Fatalf("Number of interfaces mismatch: got: %d, want: %d", numInterfaces, params.NumLAGInterfaces+numPorts) + if numInterfaces < params.NumLAGInterfaces+numPorts { + t.Fatalf("Number of interfaces mismatch: got: %d, want>= %d", numInterfaces, params.NumLAGInterfaces+numPorts) } numBGPNeighbors := 0 for _, networkInterface := range root.NetworkInstance { diff --git a/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/metadata.textproto b/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/metadata.textproto index b07a519551a..3a70a63fca6 100644 --- a/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/metadata.textproto +++ b/feature/platform/controllercard/tests/SetRequest_GetRequest_controller_card_switchover/metadata.textproto @@ -4,7 +4,7 @@ uuid: "e2b5d167-5536-475b-ba26-8939a15ffb0e" plan_id: "gNMI-1.19" description: "ConfigPush and ConfigPull after Control Card switchover" -testbed: TESTBED_DUT +testbed: TESTBED_DUT_ATE_4LINKS platform_exceptions: { platform: { vendor: ARISTA @@ -15,3 +15,11 @@ platform_exceptions: { default_network_instance: "default" } } +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + } +} diff --git a/feature/platform/controllercard/tests/controller_card_redundancy_test/controller_card_redundancy_test.go b/feature/platform/controllercard/tests/controller_card_redundancy_test/controller_card_redundancy_test.go index 56555039f44..f3cc86af88b 100644 --- a/feature/platform/controllercard/tests/controller_card_redundancy_test/controller_card_redundancy_test.go +++ b/feature/platform/controllercard/tests/controller_card_redundancy_test/controller_card_redundancy_test.go @@ -118,9 +118,31 @@ func testControllerCardSwitchover(t *testing.T, dut *ondatra.DUTDevice, controll t.Errorf("Get rpStandbyAfterSwitch: got %v, want %v", got, want) } + // Validate controller card last switchover time + lastSwitchoverTime := gnmi.OC().Component(rpActiveAfterSwitch).LastSwitchoverTime() + lastSwitchoverTimeCard := gnmi.Get(t, dut, lastSwitchoverTime.State()) + if !(gnmi.Lookup(t, dut, lastSwitchoverTime.State()).IsPresent()) { + t.Errorf("Controller card last switchover time is not returning a valid value for %s", lastSwitchoverTime.State()) + } + t.Logf("The value of last switchover time is %v", lastSwitchoverTimeCard) + // Validate controller card last switchover reason trigger + lastSwitchoverReasonTrigger := gnmi.OC().Component(rpActiveAfterSwitch).LastSwitchoverReason().Trigger() + lastSwitchoverReasonTriggerCard := gnmi.Get(t, dut, lastSwitchoverReasonTrigger.State()) + if !(gnmi.Lookup(t, dut, lastSwitchoverReasonTrigger.State()).IsPresent()) { + t.Errorf("Controller card last switchover reason trigger is not returning a valid value for %s", lastSwitchoverReasonTrigger.State()) + } + t.Logf("The value of last switchover reason trigger is %v", lastSwitchoverReasonTriggerCard) + // Validate controller card last switchover reason details + lastSwitchoverReasonDetails := gnmi.OC().Component(rpActiveAfterSwitch).LastSwitchoverReason().Details() + lastSwitchoverReasonDetailsCard := gnmi.Get(t, dut, lastSwitchoverReasonDetails.State()) + if !(gnmi.Lookup(t, dut, lastSwitchoverReasonDetails.State()).IsPresent()) { + t.Errorf("Controller card last switchover reason details is not returning a valid value for %s", lastSwitchoverReasonDetails.State()) + } + t.Logf("The value of last switchover reason details is %v", lastSwitchoverReasonDetailsCard) + // Verify that all controller_cards has switchover-ready=TRUE switchoverReadyActiverp := gnmi.OC().Component(rpActiveAfterSwitch).SwitchoverReady() - switchoverReadyStandbyrp := gnmi.OC().Component(rpActiveAfterSwitch).SwitchoverReady() + switchoverReadyStandbyrp := gnmi.OC().Component(rpStandbyAfterSwitch).SwitchoverReady() gnmi.Await(t, dut, switchoverReadyActiverp.State(), 20*time.Minute, true) gnmi.Await(t, dut, switchoverReadyStandbyrp.State(), 20*time.Minute, true) t.Logf("SwitchoverReady().Get(t): %v", gnmi.Get(t, dut, switchoverReady.State())) @@ -170,27 +192,6 @@ func testControllerCardInventory(t *testing.T, dut *ondatra.DUTDevice, controlle t.Errorf("Controller card redundant role is not returning a valid value for %s", redundantRole.State()) } t.Logf("The value of redundant role is %v", redundantRoleCard) - // Validate controller card last switchover time - lastSwitchoverTime := gnmi.OC().Component(controllerCard).LastSwitchoverTime() - lastSwitchoverTimeCard := gnmi.Get(t, dut, lastSwitchoverTime.State()) - if !(gnmi.Lookup(t, dut, lastSwitchoverTime.State()).IsPresent()) { - t.Errorf("Controller card last switchover time is not returning a valid value for %s", lastSwitchoverTime.State()) - } - t.Logf("The value of last switchover time is %v", lastSwitchoverTimeCard) - // Validate controller card last switchover reason trigger - lastSwitchoverReasonTrigger := gnmi.OC().Component(controllerCard).LastSwitchoverReason().Trigger() - lastSwitchoverReasonTriggerCard := gnmi.Get(t, dut, lastSwitchoverReasonTrigger.State()) - if !(gnmi.Lookup(t, dut, lastSwitchoverReasonTrigger.State()).IsPresent()) { - t.Errorf("Controller card last switchover reason trigger is not returning a valid value for %s", lastSwitchoverReasonTrigger.State()) - } - t.Logf("The value of last switchover reason trigger is %v", lastSwitchoverReasonTriggerCard) - // Validate controller card last switchover reason details - lastSwitchoverReasonDetails := gnmi.OC().Component(controllerCard).LastSwitchoverReason().Details() - lastSwitchoverReasonDetailsCard := gnmi.Get(t, dut, lastSwitchoverReasonDetails.State()) - if !(gnmi.Lookup(t, dut, lastSwitchoverReasonDetails.State()).IsPresent()) { - t.Errorf("Controller card last switchover reason details is not returning a valid value for %s", lastSwitchoverReasonDetails.State()) - } - t.Logf("The value of last switchover reason details is %v", lastSwitchoverReasonDetailsCard) // Validate controller card last reboot time lastRebootTime := gnmi.OC().Component(controllerCard).LastRebootTime() lastRebootTimeCard := gnmi.Get(t, dut, lastRebootTime.State()) diff --git a/feature/platform/fabric/feature.textproto b/feature/platform/fabric/feature.textproto deleted file mode 100644 index b86f5634e1f..00000000000 --- a/feature/platform/fabric/feature.textproto +++ /dev/null @@ -1,54 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "platform_fabric" - version: 1 -} - -# Classifiers -config_path { - path: "/components/component/fabric/config/power-admin-state" -} -telemetry_path { - path: "/components/component/fabric/state/power-admin-state" -} -telemetry_path { - path: "/components/component/fabric/state/power-admin-state" -} -telemetry_path { - path: "/components/component/state/description" -} -telemetry_path { - path: "/components/component/state/hardware-version" -} -telemetry_path { - path: "/components/component/state/id" -} -telemetry_path { - path: "/components/component/state/mfg-name" -} -telemetry_path { - path: "/components/component/state/name" -} -telemetry_path { - path: "/components/component/state/oper-status" -} -telemetry_path { - path: "/components/component/state/parent" -} -telemetry_path { - path: "/components/component/state/part-no" -} -telemetry_path { - path: "/components/component/state/serial-no" -} -telemetry_path { - path: "/components/component/state/type" -} -telemetry_path { - path: "/components/component/state/location" -} -telemetry_path { - path: "/components/component/state/last-reboot-time" -} diff --git a/feature/platform/fabric/otg_tests/fabric_redundancy_test/fabric_redundancy_test.go b/feature/platform/fabric/otg_tests/fabric_redundancy_test/fabric_redundancy_test.go index f4c3cbe8dd1..266adb05f84 100644 --- a/feature/platform/fabric/otg_tests/fabric_redundancy_test/fabric_redundancy_test.go +++ b/feature/platform/fabric/otg_tests/fabric_redundancy_test/fabric_redundancy_test.go @@ -250,6 +250,10 @@ func testFabricInventory(t *testing.T, dut *ondatra.DUTDevice, fabrics []string, powerAdminStateStateKey := strings.Join([]string{fabric, "state/power-admin-state"}, ":") /* fabricLeafOrValuePresent: Key: fabric:leaf, Value: []any{isLeafPresent, leafValue} */ + if deviations.ConfigLeafCreateRequired(dut) { + gnmi.Replace(t, dut, powerAdminState.Config(), oc.Platform_ComponentPowerType_POWER_ENABLED) + gnmi.Await(t, dut, gnmi.OC().Component(fabric).Fabric().PowerAdminState().State(), time.Minute, oc.Platform_ComponentPowerType_POWER_ENABLED) + } fabricLeafOrValuePresent[descriptionKey] = []any{gnmi.Lookup(t, dut, description.State()).IsPresent()} fabricLeafOrValuePresent[hardwareVersionKey] = []any{gnmi.Lookup(t, dut, hardwareVersion.State()).IsPresent()} fabricLeafOrValuePresent[idKey] = []any{gnmi.Lookup(t, dut, id.State()).IsPresent()} @@ -334,8 +338,8 @@ func testFabricLastRebootTime(t *testing.T, dut *ondatra.DUTDevice, fabrics []st gnmi.Replace(t, dut, gnmi.OC().Component(fabric).Fabric().PowerAdminState().Config(), oc.Platform_ComponentPowerType_POWER_DISABLED) gnmi.Await(t, dut, gnmi.OC().Component(fabric).Fabric().PowerAdminState().State(), time.Minute, oc.Platform_ComponentPowerType_POWER_DISABLED) - t.Logf("Waiting for 90s after power disable...") - time.Sleep(90 * time.Second) + t.Logf("Waiting for 120s after power disable...") + time.Sleep(120 * time.Second) gnmi.Replace(t, dut, gnmi.OC().Component(fabric).Fabric().PowerAdminState().Config(), oc.Platform_ComponentPowerType_POWER_ENABLED) @@ -350,8 +354,8 @@ func testFabricLastRebootTime(t *testing.T, dut *ondatra.DUTDevice, fabrics []st t.Errorf("Component %s oper-status after POWER_ENABLED, got: %v, want: %v", fabric, oper, oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE) } - t.Logf("Waiting for 90s after power enable...") - time.Sleep(90 * time.Second) + t.Logf("Waiting for 120s after power enable...") + time.Sleep(120 * time.Second) lastReboofdimeAfter := gnmi.Get(t, dut, lastReboofdime.State()) @@ -367,7 +371,7 @@ func testFabricRedundancy(t *testing.T, dut *ondatra.DUTDevice, fabrics []string od.otgConfig.Flows().Clear() od.otgConfig.Flows().Append(flowParams) od.otg.PushConfig(t, od.otgConfig) - time.Sleep(time.Second * 30) + time.Sleep(time.Second * 120) disabledFabric := "" // Create a new random source with a specific seed @@ -383,8 +387,8 @@ func testFabricRedundancy(t *testing.T, dut *ondatra.DUTDevice, fabrics []string gnmi.Replace(t, dut, gnmi.OC().Component(disabledFabric).Fabric().PowerAdminState().Config(), oc.Platform_ComponentPowerType_POWER_DISABLED) gnmi.Await(t, dut, gnmi.OC().Component(disabledFabric).Fabric().PowerAdminState().State(), time.Minute, oc.Platform_ComponentPowerType_POWER_DISABLED) - t.Logf("Waiting for 90s after power disable...") - time.Sleep(90 * time.Second) + t.Logf("Waiting for 120s after power disable...") + time.Sleep(120 * time.Second) od.otg.StartProtocols(t) od.waitInterface(t) @@ -443,8 +447,8 @@ func testFabricRedundancy(t *testing.T, dut *ondatra.DUTDevice, fabrics []string t.Errorf("Component %s oper-status after POWER_ENABLED, got: %v, want: %v", disabledFabric, oper, oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE) } - t.Logf("Waiting for 90s after power enable...") - time.Sleep(90 * time.Second) + t.Logf("Waiting for 120s after power enable...") + time.Sleep(120 * time.Second) } diff --git a/feature/platform/fabric/otg_tests/fabric_redundancy_test/metadata.textproto b/feature/platform/fabric/otg_tests/fabric_redundancy_test/metadata.textproto index 910e2ef6e30..ac90d0f5aba 100644 --- a/feature/platform/fabric/otg_tests/fabric_redundancy_test/metadata.textproto +++ b/feature/platform/fabric/otg_tests/fabric_redundancy_test/metadata.textproto @@ -24,3 +24,11 @@ platform_exceptions: { omit_l2_mtu: true } } +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + config_leaf_create_required: true + } +} diff --git a/feature/platform/tests/breakout_configuration/README.md b/feature/platform/tests/breakout_configuration/README.md index 6c336acfa2b..1f270f400fb 100644 --- a/feature/platform/tests/breakout_configuration/README.md +++ b/feature/platform/tests/breakout_configuration/README.md @@ -13,6 +13,39 @@ Validate Interface breakout configuration. * Verify correct interface state and speed reported * Verify that DUT responds to ARP/ICMP on all tested interfaces +### Canonical OC +```json +{ + "components": { + "component": [ + { + "config": { + "name": "linecard" + }, + "name": "linecard", + "port": { + "breakout-mode": { + "groups": { + "group": [ + { + "config": { + "breakout-speed": "SPEED_100GB", + "index": 0, + "num-breakouts": 4, + "num-physical-channels": 2 + }, + "index": 0 + } + ] + } + } + } + } + ] + } +} +``` + ## OpenConfig Path and RPC Coverage The below yaml defines the OC paths intended to be covered by this test. OC diff --git a/feature/platform/tests/breakout_configuration/breakout_base_test.go b/feature/platform/tests/breakout_configuration/breakout_base_test.go index 833a40178d8..f13b14b6928 100644 --- a/feature/platform/tests/breakout_configuration/breakout_base_test.go +++ b/feature/platform/tests/breakout_configuration/breakout_base_test.go @@ -12,6 +12,7 @@ import ( "strings" "testing" + "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" spb "github.com/openconfig/gnoi/system" "github.com/openconfig/ondatra" @@ -63,10 +64,16 @@ func isBreakoutSupported(t *testing.T, dut *ondatra.DUTDevice, port string, numB // verifyBreakout checks if the breakout configuration matches the expected values. // It reports errors to the testing object if there is a mismatch. -func verifyBreakout(index uint8, numBreakoutsWant uint8, numBreakoutsGot uint8, breakoutSpeedWant string, breakoutSpeedGot string, numPhysicalChannelsWant uint8, numPhysicalChannelsGot uint8, t *testing.T) { +func verifyBreakout(dut *ondatra.DUTDevice, index uint8, numBreakoutsWant uint8, numBreakoutsGot uint8, breakoutSpeedWant string, breakoutSpeedGot string, numPhysicalChannelsWant uint8, numPhysicalChannelsGot uint8, t *testing.T) { // Ensure that the index is set to the expected value (1 in this case). - if index != uint8(1) { - t.Errorf("Index: got %v, want 1", index) + if dut.Vendor() == ondatra.CISCO { + if index != uint8(0) { + t.Errorf("Index: got %v, want 0", index) + } + } else { + if index != uint8(1) { + t.Errorf("Index: got %v, want 1", index) + } } // Check if the number of breakouts configured matches what was expected. if numBreakoutsGot != numBreakoutsWant { @@ -77,8 +84,10 @@ func verifyBreakout(index uint8, numBreakoutsWant uint8, numBreakoutsGot uint8, t.Errorf("Breakout speed configured: got %v, want %v", breakoutSpeedGot, breakoutSpeedWant) } // Verify that the number of physical channels configured matches the expected value. - if numPhysicalChannelsGot != numPhysicalChannelsWant { - t.Errorf("Number of physical channels configured: got %v, want %v", numPhysicalChannelsGot, numPhysicalChannelsWant) + if !deviations.NumPhysyicalChannelsUnsupported(dut) { + if numPhysicalChannelsGot != numPhysicalChannelsWant { + t.Errorf("Number of physical channels configured: got %v, want %v", numPhysicalChannelsGot, numPhysicalChannelsWant) + } } } @@ -131,21 +140,21 @@ func IncrementIPNetwork(ipStr string, numBreakouts uint8, isIPv4 bool, lastOctet // Example being 4x100 parent port would be FourHundredGigE0/0/0/10 this will find and return // the newly broken out ports of OneHundredGigE0/0/0/0/10/0-4 func findNewPortNames(dut *ondatra.DUTDevice, t *testing.T, originalPortName string, numBreakouts uint8) ([]string, error) { - // Fetch the current state of all interfaces from the device using gNMI. + // Input originalPortName is already a breakout port, e.g., "Ethernet5/3/5". + // We need to construct sibling names based on numBreakouts. + lastSlashIndex := strings.LastIndex(originalPortName, "/") switch dut.Vendor() { case ondatra.CISCO: - intfs := gnmi.Get(t, dut, gnmi.OC().InterfaceMap().State()) - - // Split the original port name by '/' to extract the correct index (third-last segment in this case). - portSegments := strings.Split(originalPortName, "/") - if len(portSegments) < 4 { - return nil, fmt.Errorf("invalid port name format: %v", originalPortName) + if lastSlashIndex == -1 { + return nil, fmt.Errorf("Cisco: Invalid port name format: %v, expected at least one '/'", originalPortName) } - portIndex := portSegments[len(portSegments)-2] // Get the third-last segment, which is "30" - + basePortName := originalPortName[:lastSlashIndex] + t.Logf("basePortName is: %s", basePortName) + intfs := gnmi.Get(t, dut, gnmi.OC().InterfaceMap().State()) // Define a pattern to match breakout port names that include the original port index. - breakoutPattern := fmt.Sprintf(`\w+/\d+/\d+/%s/\d+`, portIndex) + breakoutPattern := fmt.Sprintf(`^%s/\d+$`, regexp.QuoteMeta(basePortName)) + t.Logf("breakoutPattern is: %s", breakoutPattern) // Compile the pattern into a regular expression. re := regexp.MustCompile(breakoutPattern) @@ -153,11 +162,12 @@ func findNewPortNames(dut *ondatra.DUTDevice, t *testing.T, originalPortName str // Loop through all interfaces and collect those that match the breakout pattern var newPortNames []string for intfName := range intfs { + t.Logf("intfName is: %s", intfName) if re.MatchString(intfName) { newPortNames = append(newPortNames, intfName) } } - + sortBreakoutPorts(newPortNames) // Check if the number of new ports found is equal to the number of breakouts expected. if len(newPortNames) != int(numBreakouts) { return nil, fmt.Errorf("expected to find %d new ports, found %d", numBreakouts, len(newPortNames)) @@ -165,19 +175,26 @@ func findNewPortNames(dut *ondatra.DUTDevice, t *testing.T, originalPortName str return newPortNames, nil - // Returns all reserved ports (We are only reserving the breakout ports for the test) case ondatra.ARISTA: - portsAll := dut.Ports() - newPortNames := []string{} - for _, port := range portsAll { - newPortNames = append(newPortNames, port.Name()) + + // Ensure there is a slash and it's not the last character (i.e., there's a number after it). + if lastSlashIndex == -1 || lastSlashIndex == len(originalPortName)-1 { + return nil, fmt.Errorf("Arista: invalid port name format '%s'. Expected format like 'Prefix/SubPortNumber' (e.g., 'Ethernet1/2/3')", originalPortName) + } + + baseNamePrefix := originalPortName[:lastSlashIndex+1] + // startSubPortStr := originalPortName[lastSlashIndex+1:] // E.g., "5" + + var newPortNames []string + for currentSubPortNum := 1; currentSubPortNum <= int(numBreakouts); currentSubPortNum++ { + newPortName := baseNamePrefix + strconv.Itoa(currentSubPortNum) + newPortNames = append(newPortNames, newPortName) } return newPortNames, nil + default: - t.Fatalf("Unsupported vendor %s. Need to add breakout component names.", dut.Vendor()) + return nil, fmt.Errorf("unsupported DUT vendor: %v", dut.Vendor()) } - - return nil, nil } // fetchResponses will fetch the ping response diff --git a/feature/platform/tests/breakout_configuration/breakout_test.go b/feature/platform/tests/breakout_configuration/breakout_test.go index 4d1284835ed..619cb60c176 100644 --- a/feature/platform/tests/breakout_configuration/breakout_test.go +++ b/feature/platform/tests/breakout_configuration/breakout_test.go @@ -24,10 +24,11 @@ import ( const ( maxPingRetries = 3 // Set the number of retry attempts - schemaValue = 1 + //schemaValue = 1 ) var ( + schemaValue = 1 dutPortName string foundExpectedInterfaceFlag bool = false breakOutCompName string @@ -190,6 +191,13 @@ func TestPlatformBreakoutConfig(t *testing.T) { var Dutipv4Subnets []string var Ateipv4Subnets []string + // Decide which group index to use based on the device type + switch dut.Vendor() { + case ondatra.CISCO: + schemaValue = 0 + default: + schemaValue = 1 + } cases := []struct { numbreakouts uint8 breakoutspeed oc.E_IfEthernet_ETHERNET_SPEED @@ -271,11 +279,20 @@ func TestPlatformBreakoutConfig(t *testing.T) { for _, componentName := range componentNameList { t.Logf("Starting Test for %s %v", componentName, tc) - configContainer := &oc.Component_Port_BreakoutMode_Group{ - Index: ygot.Uint8(1), - NumBreakouts: ygot.Uint8(tc.numbreakouts), - BreakoutSpeed: oc.E_IfEthernet_ETHERNET_SPEED(tc.breakoutspeed), - NumPhysicalChannels: ygot.Uint8(tc.numPhysicalChannels), + var configContainer *oc.Component_Port_BreakoutMode_Group + if !deviations.NumPhysyicalChannelsUnsupported(dut) { + configContainer = &oc.Component_Port_BreakoutMode_Group{ + Index: ygot.Uint8(1), + NumBreakouts: ygot.Uint8(tc.numbreakouts), + BreakoutSpeed: oc.E_IfEthernet_ETHERNET_SPEED(tc.breakoutspeed), + NumPhysicalChannels: ygot.Uint8(tc.numPhysicalChannels), + } + } else { + configContainer = &oc.Component_Port_BreakoutMode_Group{ + Index: ygot.Uint8(0), + NumBreakouts: ygot.Uint8(tc.numbreakouts), + BreakoutSpeed: oc.E_IfEthernet_ETHERNET_SPEED(tc.breakoutspeed), + } } groupContainer := &oc.Component_Port_BreakoutMode{Group: map[uint8]*oc.Component_Port_BreakoutMode_Group{1: configContainer}} breakoutContainer := &oc.Component_Port{BreakoutMode: groupContainer} @@ -301,19 +318,25 @@ func TestPlatformBreakoutConfig(t *testing.T) { // Apply configuration gnmi.Update(t, dut, gnmi.OC().Component(componentName).Name().Config(), componentName) - gnmi.Delete(t, dut, gnmi.OC().Component(componentName).Port().BreakoutMode().Group(schemaValue).Config()) - path := gnmi.OC().Component(componentName).Port().BreakoutMode().Group(schemaValue) + gnmi.Delete(t, dut, gnmi.OC().Component(componentName).Port().BreakoutMode().Group(uint8(schemaValue)).Config()) + path := gnmi.OC().Component(componentName).Port().BreakoutMode().Group(uint8(schemaValue)) gnmi.Replace(t, dut, path.Config(), configContainer) t.Run(fmt.Sprintf("Subscribe//component[%v]/config/port/breakout-mode/group[%v]", componentName, schemaValue), func(t *testing.T) { - state := gnmi.OC().Component(componentName).Port().BreakoutMode().Group(schemaValue) + state := gnmi.OC().Component(componentName).Port().BreakoutMode().Group(uint8(schemaValue)) groupDetails := gnmi.Get(t, dut, state.Config()) index := *groupDetails.Index numBreakouts := *groupDetails.NumBreakouts breakoutSpeed := groupDetails.BreakoutSpeed - numPhysicalChannels := *groupDetails.NumPhysicalChannels - verifyBreakout(index, tc.numbreakouts, numBreakouts, tc.breakoutspeed.String(), + var numPhysicalChannels uint8 + if !deviations.NumPhysyicalChannelsUnsupported(dut) { + numPhysicalChannels = *groupDetails.NumPhysicalChannels + } else { + numPhysicalChannels = *ygot.Uint8(0) + } + + verifyBreakout(dut, index, tc.numbreakouts, numBreakouts, tc.breakoutspeed.String(), breakoutSpeed.String(), tc.numPhysicalChannels, numPhysicalChannels, t) }) @@ -431,9 +454,9 @@ func TestPlatformBreakoutConfig(t *testing.T) { t.Run(fmt.Sprintf("Delete//component[%v]/config/port/breakout-mode/group[1]/config", componentName), func(t *testing.T) { - path := gnmi.OC().Component(componentName).Port().BreakoutMode().Group(schemaValue) + path := gnmi.OC().Component(componentName).Port().BreakoutMode().Group(uint8(schemaValue)) gnmi.Delete(t, dut, path.Config()) - verifyDelete(t, dut, componentName, schemaValue) + verifyDelete(t, dut, componentName, uint8(schemaValue)) }) } }) diff --git a/feature/platform/tests/breakout_configuration/metadata.textproto b/feature/platform/tests/breakout_configuration/metadata.textproto index 43f734a8b69..4bf8750593a 100644 --- a/feature/platform/tests/breakout_configuration/metadata.textproto +++ b/feature/platform/tests/breakout_configuration/metadata.textproto @@ -10,6 +10,6 @@ platform_exceptions: { } deviations: { verify_expected_breakout_supported_config: true - + num_physical_channels_unsupported: true } } diff --git a/feature/platform/tests/breakout_subscription_test/README.md b/feature/platform/tests/breakout_subscription_test/README.md index 1bf7889e0ba..be7171507cd 100644 --- a/feature/platform/tests/breakout_subscription_test/README.md +++ b/feature/platform/tests/breakout_subscription_test/README.md @@ -41,6 +41,17 @@ OnChange Subscription Test for Breakout Interfaces * Issue a reboot to the device and check if all the paths can be subscribed to. +### PLT-1.3.5 Check Notifications for updates on a new Linecard power up + + * Clear the old subscription and make a gNMI power down to any one of the linecard + * Now create a new Subscription to the device + * Issue a gNMI powerup to the earlier powered down card + * Validate if the received Notifications have updates for change in port state of the links that powered up + +#### Canonical OC +```json +{} +``` ## OpenConfig Path and RPC Coverage diff --git a/feature/platform/tests/breakout_subscription_test/breakout_subscription_test.go b/feature/platform/tests/breakout_subscription_test/breakout_subscription_test.go index 868be8cf10e..e4c28879de4 100644 --- a/feature/platform/tests/breakout_subscription_test/breakout_subscription_test.go +++ b/feature/platform/tests/breakout_subscription_test/breakout_subscription_test.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "net" + "regexp" "sort" "strconv" "strings" @@ -784,6 +785,141 @@ func verifyUpdateValue(t testing.TB, notifications []*gpb.Notification, expected } } +func lineCardUp(t testing.TB, dut *ondatra.DUTDevice, fpc string) { + c := gnmi.OC().Component(fpc) + config := c.Linecard().PowerAdminState().Config() + t.Logf("Starting %s POWER_ENABLED", fpc) + start := time.Now() + gnmi.Replace(t, dut, config, oc.Platform_ComponentPowerType_POWER_ENABLED) + oper, ok := gnmi.Await(t, dut, c.OperStatus().State(), 20*time.Minute, oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE).Val() + if !ok { + t.Errorf("Component %s oper-status after POWER_ENABLED, got: %v, want: %v", fpc, oper, oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE) + } + t.Logf("Component %s, oper-status after %f minutes: %v", fpc, time.Since(start).Minutes(), oper) +} + +func findFpcFromPort(t testing.TB, portArray []string, dut *ondatra.DUTDevice) ([]string, error) { + t.Helper() + var fpcArray []string + + for _, portName := range portArray { + if dut.Vendor() == ondatra.ARISTA { + re := regexp.MustCompile(`^[A-Za-z]+(\d+)/(\d+)/\d+(?::\d+)?$`) + match := re.FindStringSubmatch(portName) + if match == nil { + return nil, fmt.Errorf("invalid port name format: %s", portName) + } + fpcArray = append(fpcArray, fmt.Sprintf("FPC%s", match[1])) + } + if dut.Vendor() == ondatra.CISCO { + re := regexp.MustCompile(`^HundredGigE+(\d+)/(\d+)/\d+/\d+(?::\d+)?$`) + match := re.FindStringSubmatch(portName) + if match == nil { + return nil, fmt.Errorf("invalid port name format: %s", portName) + } + fpcArray = append(fpcArray, fmt.Sprintf("FPC%s", match[2])) + } + if dut.Vendor() == ondatra.JUNIPER { + re := regexp.MustCompile(`^[a-z]+-(\d+)/\d+/\d+(?::\d+)?$`) + match := re.FindStringSubmatch(portName) + if match == nil { + return nil, fmt.Errorf("invalid port name format: %s", portName) + } + fpcArray = append(fpcArray, fmt.Sprintf("FPC%s", match[1])) + t.Logf("fpcArray: %v", fpcArray) + t.Logf("portName: %v", portName) + t.Logf("match: %v", match[1]) + } + if dut.Vendor() == ondatra.NOKIA { + re := regexp.MustCompile(`ethernet-(\d+)/\d+(?::(\d+))?$`) + match := re.FindStringSubmatch(portName) + if match == nil { + return nil, fmt.Errorf("invalid port name format: %s", portName) + } + fpcArray = append(fpcArray, fmt.Sprintf("FPC%s", match[1])) + } + } + return fpcArray, nil +} +func verifyNotificationPathsForPortUpdates(t *testing.T, notifications []*gpb.Notification, selectedFpc string) { + t.Helper() + for _, notification := range notifications { + path := "" + for _, elem := range notification.GetPrefix().GetElem() { + path += "/" + elem.GetName() + for key, value := range elem.GetKey() { + if value == selectedFpc { + t.Logf("Notification path: %v has update for part: %v", path, selectedFpc) + t.Logf("Key: %v, Value: %v", key, value) + return + } + } + } + } + t.Errorf("Notification is missing update for part: %v", selectedFpc) +} + +func linecardDown(t testing.TB, dut *ondatra.DUTDevice, fpc string, lcs []string) { + var validCards []string + // don't consider the empty linecard slots. + if len(lcs) > *args.NumLinecards { + for _, lc := range lcs { + empty, ok := gnmi.Lookup(t, dut, gnmi.OC().Component(lc).Empty().State()).Val() + if !ok || (ok && !empty) { + validCards = append(validCards, lc) + } + } + } else { + validCards = lcs + } + if *args.NumLinecards >= 0 && len(validCards) != *args.NumLinecards { + t.Errorf("Incorrect number of linecards: got %v, want exactly %v (specified by flag)", len(validCards), *args.NumLinecards) + } + + if got := len(validCards); got == 0 { + t.Skipf("Not enough linecards for the test on %v: got %v, want > 0", dut.Model(), got) + } + + if got := gnmi.Lookup(t, dut, gnmi.OC().Component(fpc).Removable().State()).IsPresent(); !got { + t.Fatalf("Detected non-removable line card: %v", fpc) + } + if got := gnmi.Get(t, dut, gnmi.OC().Component(fpc).Removable().State()); got { + t.Logf("Found removable line card: %v", fpc) + } + c := gnmi.OC().Component(fpc) + config := c.Linecard().PowerAdminState().Config() + t.Logf("Starting %s POWER_DISABLE", fpc) + gnmi.Replace(t, dut, config, oc.Platform_ComponentPowerType_POWER_DISABLED) + + t.Logf("Wait for 15 seconds to allow the sub component's power down process to complete") + time.Sleep(15 * time.Second) +} +func uniqueString(input []string) []string { + seen := make(map[string]struct{}) + var result []string + + for _, item := range input { + if _, exists := seen[item]; !exists { + seen[item] = struct{}{} // Mark as seen + result = append(result, item) + } + } + return result +} + +func selectFpc(t testing.TB, fpcList []string) string { + t.Helper() + var selectedFpc string + uniqueFpcList := uniqueString(fpcList) + if len(uniqueFpcList) > 1 { + sort.Strings(uniqueFpcList) + selectedFpc = uniqueFpcList[len(uniqueFpcList)-1] + } else { + t.Fatalf("No FPC found for the test") + } + return selectedFpc +} + func TestBreakoutSubscription(t *testing.T) { dut := ondatra.DUT(t, "dut") ate := ondatra.ATE(t, "ate") @@ -805,7 +941,6 @@ func TestBreakoutSubscription(t *testing.T) { stream := newSubscribeRequest(ctx, t, dut) checkSyncResponse(t, stream) t.Run("PLT-1.2.1 Check response after a triggered interface state change", func(t *testing.T) { - setDUTInterfaceWithState(t, dut, tc.dutPorts[0], false) setDUTInterfaceWithState(t, dut, tc.dutPorts[2], false) time.Sleep(2 * time.Second) @@ -878,4 +1013,29 @@ func TestBreakoutSubscription(t *testing.T) { defer stream.CloseSend() defer ctx.Done() }) + // Check response after a triggered breakout module reboot + t.Run("PLT-1.2.5 Check response after a triggered breakout module reboot", func(t *testing.T) { + intfsOperStatusUPBeforeReboot := helpers.FetchOperStatusUPIntfs(t, dut, *args.CheckInterfacesInBinding) + t.Logf("intfsOperStatusUPBeforeReboot: %v", intfsOperStatusUPBeforeReboot) + fpcList, err := findFpcFromPort(t, intfsOperStatusUPBeforeReboot, dut) + if err != nil { + t.Fatalf("Failed to find FPC from port: %v", err) + } + t.Logf("fpcList: %v", fpcList) + selectedFpc := selectFpc(t, fpcList) + t.Logf("selectedFpc: %v", selectedFpc) + lcs := components.FindComponentsByType(t, dut, oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_LINECARD) + linecardDown(t, dut, selectedFpc, lcs) + stream := newSubscribeRequest(ctx, t, dut) + checkSyncResponse(t, stream) + lineCardUp(t, dut, selectedFpc) + updateTimeout := 10 * time.Minute + receivedNotifications, err := recieveUpdateWithTimeout(ctx, t, dut, stream, subscribedUpdates, updateTimeout) + if err != nil { + t.Logf("Received error(possibly end of updates): %v", err) + } + verifyNotificationPathsForPortUpdates(t, receivedNotifications, selectedFpc) + defer stream.CloseSend() + defer ctx.Done() + }) } diff --git a/feature/platform/tests/optics_power_and_bias_current_test/README.md b/feature/platform/tests/optics_power_and_bias_current_test/README.md index 717752e2187..5c133ab032c 100644 --- a/feature/platform/tests/optics_power_and_bias_current_test/README.md +++ b/feature/platform/tests/optics_power_and_bias_current_test/README.md @@ -77,39 +77,57 @@ to Automatic Test Equipment (ATE). * Verify /interfaces/interface/state/oper-status is UP. * Repeat Step1 and Step2. -## Config Parameter coverage - -* /interfaces/interface/config/enabled -* /components/component/transceiver/state/enabled (transceiver 3.3V power supply on/off) - -## Telemetry Parameter coverage - -* /components/component/transceiver/physical-channels/channel/state/input-power/instant -* /components/component/transceiver/physical-channels/channel/state/output-power/instant -* /components/component/transceiver/physical-channels/channel/state/laser-bias-current/instant -* /components/component/state/temperature/instant -* /components/component/state/mfg-name -* /components/component/transceiver/state/form-factor -* /components/component/state/serial-no -* /components/component/state/part-no -* /components/component/state/firmware-version -* /components/component/transceiver/thresholds/threshold/state/output-power-lower -* /components/component/transceiver/thresholds/threshold/state/output-power-upper -* /components/component/transceiver/thresholds/threshold/state/input-power-lower -* /components/component/transceiver/thresholds/threshold/state/input-power-upper -* /components/component/transceiver/thresholds/threshold/state/module-temperature-lower -* /components/component/transceiver/thresholds/threshold/state/module-temperature-upper -* /components/component/transceiver/thresholds/threshold/state/laser-bias-current-lower -* /components/component/transceiver/thresholds/threshold/state/laser-bias-current-upper -* /components/component/transceiver/thresholds/threshold/state/severity - ## OpenConfig Path and RPC Coverage The below yaml defines the OC paths intended to be covered by this test. ```yaml +paths: + # Config Parameter coverage + /components/component/transceiver/config/enabled: + platform_type: ["TRANSCEIVER"] + /interfaces/interface/config/enabled: + + # Telemetry Parameter coverage + /components/component/state/firmware-version: + platform_type: ["TRANSCEIVER"] + /components/component/state/mfg-name: + platform_type: ["TRANSCEIVER"] + /components/component/state/part-no: + platform_type: ["TRANSCEIVER"] + /components/component/state/serial-no: + platform_type: ["TRANSCEIVER"] + /components/component/state/temperature/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/input-power/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/laser-bias-current/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/output-power/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/state/form-factor: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/input-power-lower: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/input-power-upper: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/laser-bias-current-lower: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/laser-bias-current-upper: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/module-temperature-lower: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/module-temperature-upper: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/output-power-lower: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/output-power-upper: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/thresholds/threshold/state/severity: + platform_type: ["TRANSCEIVER"] + rpcs: gnmi: gNMI.Set: gNMI.Subscribe: -``` \ No newline at end of file +``` diff --git a/feature/platform/tests/parent_component_validation/README.md b/feature/platform/tests/parent_component_validation/README.md index cc14f995c17..e1f94d1713d 100644 --- a/feature/platform/tests/parent_component_validation/README.md +++ b/feature/platform/tests/parent_component_validation/README.md @@ -34,6 +34,44 @@ rpcs: gNMI.Get: ``` +#### Canonical OC +```json +{ + "components": { + "component": [ + { + "config": { + "name": "Ethernet-1/1-Port" + }, + "name": "Ethernet-1/1-Port", + "state": { + "parent": "SwitchChip1/0" + } + }, + { + "config": { + "name": "SwitchChip1/0" + }, + "name": "SwitchChip1/0" + } + ] + }, + "interfaces": { + "interface": [ + { + "config": { + "name": "ethernet-1/1" + }, + "name": "ethernet-1/1", + "state": { + "hardware-port": "Ethernet-1/1-Port" + } + } + ] + } +} +``` + ## Minimum DUT Platform Requirement - FFF diff --git a/feature/platform/tests/parent_component_validation/parent_component_validation_test.go b/feature/platform/tests/parent_component_validation/parent_component_validation_test.go index eecfd97e03f..db6e08e55dd 100644 --- a/feature/platform/tests/parent_component_validation/parent_component_validation_test.go +++ b/feature/platform/tests/parent_component_validation/parent_component_validation_test.go @@ -54,19 +54,24 @@ func TestInterfaceParentComponent(t *testing.T) { { desc: "Port1", port: "port1", - pattern: "^(SwitchChip|NPU[0-9]|[0-9]/[0-9]/CPU[0-9]-NPU[0-9])$", + pattern: "^(SwitchChip(?:[0-9]/[0-9])?|NPU[0-9]|[0-9]/[0-9]/CPU[0-9]-NPU[0-9]|FPC[0-9]+:PIC[0-9]:NPU[0-9])$", }, { desc: "Port2", port: "port2", - pattern: "^(SwitchChip|NPU[0-9]|[0-9]/[0-9]/CPU[0-9]-NPU[0-9])$", + pattern: "^(SwitchChip(?:[0-9]/[0-9])?|NPU[0-9]|[0-9]/[0-9]/CPU[0-9]-NPU[0-9]|FPC[0-9]+:PIC[0-9]:NPU[0-9])$", }, } for _, tc := range cases { t.Run(tc.desc, func(t *testing.T) { dp := dut.Port(t, tc.port) - parent := checkParentComponent(t, dut, dp.Name()) + hardwarePortName := gnmi.Lookup(t, dut, gnmi.OC().Interface(dp.Name()).HardwarePort().State()) + hVal, present := hardwarePortName.Val() + if !present { + t.Errorf("Hardware port NOT found for interface: %s", dp.Name()) + } + parent := checkParentComponent(t, dut, hVal) t.Logf("Interface %s parent is %s", dp.Name(), parent) if ok, err := regexp.MatchString(tc.pattern, parent); !ok || err != nil { t.Errorf("Interface %s parent did not match pattern %s: %v", dp.Name(), tc.pattern, err) diff --git a/feature/platform/tests/storage/metadata.textproto b/feature/platform/tests/storage/metadata.textproto new file mode 100644 index 00000000000..c425230a7f5 --- /dev/null +++ b/feature/platform/tests/storage/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "18e8fca1-9e13-4226-8e91-5d00d336938c" +plan_id: "Storage-1.1" +description: "Storage File System Check" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/platform/transceiver/tests/zr_800_platform_paths_test/README.md b/feature/platform/transceiver/tests/zr_800_platform_paths_test/README.md new file mode 100644 index 00000000000..0ad1ee421ab --- /dev/null +++ b/feature/platform/transceiver/tests/zr_800_platform_paths_test/README.md @@ -0,0 +1,364 @@ +# TRANSCEIVER-101: Telemetry: ZR platform OC paths streaming. + +## Summary + +Validate ZR optics module reports telemetry data for all leaves in + +```yaml + /components/component/: + platform_type: ["PORT"] + /components/component/: + platform_type: ["TRANSCEIVER"] + /components/component/: + platform_type: ["OPTICAL_CHANNEL"] +``` + +## Procedure + +* Connect two ZR interfaces using a duplex LC fiber jumper such that TX + output power of one is the RX input power of the other module. + +* To establish a point to point ZR link ensure the following: + * Both transceivers state is enabled + * Both transceivers are set to a valid operational mode + example 1. + * Both transceivers are set to a valid target TX output power + example -7 dBm. + * Both transceivers are tuned to a valid centre frequency + example 196.1 THz. + +* With the ZR link established as explained above, wait until + both interfaces oper-status are UP and all the min/avg/max values are + populated. Then verify that the following ZR transceiver telemetry paths + exist and are streamed valid values for both ZR optics. + +* Emulate flaps with the following procedure: + * Enable a pair of ZR interfaces on the DUT as explained above. + * Disable interface and wait at least one sample interval. + * Enable interface. + +* Verify that all the static leaves (e.g., breakout-speed, part-no, ...) + are present and reports valid strings or enums before, during and after flap. + +* Verify all the other leaves reports valid value of decimal64 before, + during and after flap. For leaves with stats, ensure min <= avg/instant <= max. + + * platform/components/component/state/name + * platform/components/component/state/location + * platform/components/component/state/type + * platform/components/component/port/breakout-mode/groups/group/state/index + * platform/components/component/port/breakout-mode/groups/group/state/breakout-speed + * platform/components/component/port/breakout-mode/groups/group/state/num-breakouts + * platform/components/component/port/breakout-mode/groups/group/state/num-physical-channels + * platform/components/component/state/name + * platform/components/component/state/parent + * platform/components/component/state/location + * platform/components/component/state/removable + * platform/components/component/state/type + * platform/components/component/state/oper-status + * platform/components/component/state/temperature/instant + * platform/components/component/state/firmware-version + * platform/components/component/state/hardware-version + * platform/components/component/state/serial-no + * platform/components/component/state/part-no + * platform/components/component/state/mfg-name + * platform/components/component/state/mfg-date + * platform/components/component/transceiver/state/form-factor + * platform/components/component/transceiver/state/present + * platform/components/component/transceiver/state/connector-type + * platform/components/component/transceiver/state/supply-voltage/instant + * platform/components/component/transceiver/physical-channels/channel/state/index + * platform/components/component/transceiver/physical-channels/channel/state/output-power/instant + * platform/components/component/transceiver/physical-channels/channel/state/input-power/instant + * platform/components/component/transceiver/physical-channels/channel/state/input-power/avg + * platform/components/component/transceiver/physical-channels/channel/state/input-power/min + * platform/components/component/transceiver/physical-channels/channel/state/input-power/max + * platform/components/component/state/name + * platform/components/component/state/parent + * platform/components/component/state/type + * platform/components/component/optical-channel/state/operational-mode + * platform/components/component/optical-channel/state/frequency + * platform/components/component/optical-channel/state/target-output-power + * platform/components/component/optical-channel/state/laser-bias-current/instant + * platform/components/component/optical-channel/state/input-power/instant + * platform/components/component/optical-channel/state/input-power/avg + * platform/components/component/optical-channel/state/input-power/min + * platform/components/component/optical-channel/state/input-power/max + * platform/components/component/optical-channel/state/output-power/instant + * platform/components/component/optical-channel/state/output-power/avg + * platform/components/component/optical-channel/state/output-power/min + * platform/components/component/optical-channel/state/output-power/max + * platform/components/component/optical-channel/state/chromatic-dispersion/instant + * platform/components/component/optical-channel/state/chromatic-dispersion/avg + * platform/components/component/optical-channel/state/chromatic-dispersion/min + * platform/components/component/optical-channel/state/chromatic-dispersion/max + * platform/components/component/optical-channel/state/carrier-frequency-offset/instant + * platform/components/component/optical-channel/state/carrier-frequency-offset/avg + * platform/components/component/optical-channel/state/carrier-frequency-offset/min + * platform/components/component/optical-channel/state/carrier-frequency-offset/max + +**Note:** For min, max, and avg values, 10 second sampling is preferred. If + 10 seconds is not supported, the sampling interval used must be + specified by adding a deviation to the test. + +### Canonical OC +```json +{ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "config": { + "name": "Ethernet4/1/1", + "type": "ethernetCsmacd" + }, + "name": "Ethernet4/1/1", + "openconfig-if-ethernet:ethernet": { + "config": { + "duplex-mode": "FULL", + "port-speed": "SPEED_800GB" + } + } + } + ] + }, + "openconfig-platform:components": { + "component": [ + { + "config": { + "name": "Ethernet4/1-Port" + }, + "name": "Ethernet4/1-Port", + "openconfig-platform-port:port": { + "breakout-mode": { + "groups": { + "group": [ + { + "config": { + "breakout-speed": "openconfig-if-ethernet:SPEED_800GB", + "index": 1, + "num-breakouts": 1, + "num-physical-channels": 8 + }, + "index": 1 + } + ] + } + } + } + }, + { + "config": { + "name": "Ethernet4/1" + }, + "name": "Ethernet4/1" + }, + { + "config": { + "name": "Ethernet4/1-Optical0" + }, + "name": "Ethernet4/1-Optical0", + "openconfig-terminal-device:optical-channel": { + "config": { + "operational-mode": 1, + "frequency": "196000000", + "target-output-power": "-7" + } + } + } + ] + }, + "openconfig-terminal-device:terminal-device": { + "logical-channels": { + "channel": [ + { + "logical-channel-assignments": { + "assignment": [ + { + "config": { + "allocation": "800", + "assignment-type": "OPTICAL_CHANNEL", + "description": "OTN to optical channel assignment", + "index": 1, + "optical-channel": "Ethernet4/1-Optical0" + } + } + ] + }, + "config": { + "admin-state": "ENABLED", + "description": "OTN Logical Channel", + "index": 8000, + "logical-channel-type": "openconfig-transport-types:PROT_OTN" + } + }, + { + "ingress": { + "config": { + "interface": "Ethernet4/1/1", + "transceiver": "Ethernet4/1" + } + }, + "logical-channel-assignments": { + "assignment": [ + { + "config": { + "allocation": "800", + "assignment-type": "LOGICAL_CHANNEL", + "description": "ETH to OTN assignment", + "index": 1, + "logical-channel": 8000 + } + } + ] + }, + "config": { + "admin-state": "ENABLED", + "description": "ETH Logical Channel", + "index": 80000, + "logical-channel-type": "openconfig-transport-types:PROT_ETHERNET", + "rate-class": "openconfig-transport-types:TRIB_RATE_800G", + "trib-protocol": "openconfig-transport-types:PROT_800GE" + } + } + ] + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config Parameter coverage + /interfaces/interface/config/enabled: + /interfaces/interface/config/type: + /interfaces/interface/ethernet/config/port-speed: + /interfaces/interface/ethernet/config/duplex-mode: + /components/component/port/breakout-mode/groups/group/config/breakout-speed: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/config/num-breakouts: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/config/num-physical-channels: + platform_type: ["PORT"] + /components/component/optical-channel/config/operational-mode: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/config/frequency: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/config/target-output-power: + platform_type: ["OPTICAL_CHANNEL"] + /terminal-device/logical-channels/channel/config/admin-state: + /terminal-device/logical-channels/channel/config/description: + /terminal-device/logical-channels/channel/config/logical-channel-type: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/description: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/assignment-type: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/allocation: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/optical-channel: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/logical-channel: + /terminal-device/logical-channels/channel/ingress/config/transceiver: + /terminal-device/logical-channels/channel/ingress/config/interface: + + # Telemetry Parameter coverage + /components/component/state/name: + platform_type: ["PORT", "TRANSCEIVER", "OPTICAL_CHANNEL"] + /components/component/state/location: + platform_type: ["PORT", "TRANSCEIVER", "OPTICAL_CHANNEL"] + /components/component/state/type: + platform_type: ["PORT", "TRANSCEIVER", "OPTICAL_CHANNEL"] + /components/component/state/removable: + platform_type: ["PORT", "TRANSCEIVER", "OPTICAL_CHANNEL"] + /components/component/state/parent: + platform_type: ["PORT", "TRANSCEIVER", "OPTICAL_CHANNEL"] + /components/component/port/breakout-mode/groups/group/state/index: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/state/breakout-speed: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/state/num-breakouts: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/state/num-physical-channels: + platform_type: ["PORT"] + /components/component/state/oper-status: + platform_type: ["TRANSCEIVER"] + /components/component/state/temperature/instant: + platform_type: ["TRANSCEIVER"] + /components/component/state/firmware-version: + platform_type: ["TRANSCEIVER"] + /components/component/state/hardware-version: + platform_type: ["TRANSCEIVER"] + /components/component/state/serial-no: + platform_type: ["TRANSCEIVER"] + /components/component/state/part-no: + platform_type: ["TRANSCEIVER"] + /components/component/state/mfg-name: + platform_type: ["TRANSCEIVER"] + /components/component/state/mfg-date: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/state/form-factor: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/state/present: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/state/connector-type: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/state/supply-voltage/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/index: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/output-power/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/input-power/instant: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/input-power/avg: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/input-power/min: + platform_type: ["TRANSCEIVER"] + /components/component/transceiver/physical-channels/channel/state/input-power/max: + platform_type: ["TRANSCEIVER"] + /components/component/optical-channel/state/operational-mode: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/frequency: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/target-output-power: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/laser-bias-current/instant: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/input-power/instant: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/input-power/avg: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/input-power/min: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/input-power/max: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/output-power/instant: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/output-power/avg: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/output-power/min: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/output-power/max: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/chromatic-dispersion/instant: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/chromatic-dispersion/avg: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/chromatic-dispersion/min: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/chromatic-dispersion/max: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/carrier-frequency-offset/instant: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/carrier-frequency-offset/avg: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/carrier-frequency-offset/min: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/state/carrier-frequency-offset/max: + platform_type: ["OPTICAL_CHANNEL"] + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` + + diff --git a/feature/platform/transceiver/tests/zr_800_platform_paths_test/metadata.textproto b/feature/platform/transceiver/tests/zr_800_platform_paths_test/metadata.textproto new file mode 100644 index 00000000000..2012920c7b0 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_800_platform_paths_test/metadata.textproto @@ -0,0 +1,19 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "670a5ddb-d187-418d-a64e-e8cd8fc1140e" +plan_id: "TRANSCEIVER-101" +description: "Telemetry: ZR platform OC paths streaming." +testbed: TESTBED_DUT_800ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + channel_assignment_rate_class_parameters_unsupported: true + missing_port_to_optical_channel_component_mapping: true + otn_channel_trib_unsupported: true + otn_to_eth_assignment: true + } +} diff --git a/feature/platform/transceiver/tests/zr_800_platform_paths_test/zr_800_platform_paths_test.go b/feature/platform/transceiver/tests/zr_800_platform_paths_test/zr_800_platform_paths_test.go new file mode 100644 index 00000000000..be526c949d0 --- /dev/null +++ b/feature/platform/transceiver/tests/zr_800_platform_paths_test/zr_800_platform_paths_test.go @@ -0,0 +1,865 @@ +package zr_800_platform_paths_test + +import ( + "flag" + "fmt" + "math" + "regexp" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/samplestream" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + samplingInterval = 1 * time.Second + minAllowedCDValue = -200 + maxAllowedCDValue = 2400 + inactiveCDValue = 1.0 + inactivePower = -30.0 + powerLoss = 2.0 + powerReadingError = 1.0 + minAllowedCarrierFrequencyOffset = -1000.0 + maxAllowedCarrierFrequencyOffset = 1000.0 + inactiveCarrierFrequencyOffset = 1.0 + minAllowedLaserBiasCurrent = 0.0 + maxAllowedLaserBiasCurrent = 300.0 + inactiveLaserBiasCurrent = 0.0 + minAllowedLaserTemperature = 25.0 + maxAllowedLaserTemperature = 75.0 + inactiveLaserTemperature = 0.0 + minAllowedSupplyVoltage = 3.0 + maxAllowedSupplyVoltage = 4.0 + minAllowedTemperature = 25.0 + maxAllowedTemperature = 75.0 + errorTolerance = 0.05 + timeout = 10 * time.Minute +) + +var ( + frequencyList cfgplugins.FrequencyList + targetOpticalPowerList cfgplugins.TargetOpticalPowerList + operationalModeList cfgplugins.OperationalModeList + componentPath = "openconfig/components/component[name=%v]" +) + +type testcase struct { + desc string + path string + got any + want any + operStatus oc.E_Interface_OperStatus + minAllowed float64 + maxAllowed float64 + patternToMatch string +} + +func init() { + flag.Var(&operationalModeList, "operational_mode", "operational-mode for the channel.") + flag.Var(&frequencyList, "frequency", "frequency for the channel.") + flag.Var(&targetOpticalPowerList, "target_optical_power", "target-optical-power for the channel.") +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestComponentPaths(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + fptest.ConfigureDefaultNetworkInstance(t, dut) + + setToDefaults(t, dut) + + for _, operationalMode := range operationalModeList { + for _, frequency := range frequencyList { + for _, targetOpticalPower := range targetOpticalPowerList { + + t.Logf("\n*** Configure interfaces with Operational Mode: %v, Optical Frequency: %v, Target Power: %v\n\n\n", operationalMode, frequency, targetOpticalPower) + params := &cfgplugins.ConfigParameters{ + Enabled: true, + Frequency: frequency, + TargetOpticalPower: targetOpticalPower, + OperationalMode: operationalMode, + } + batch := &gnmi.SetBatch{} + cfgplugins.NewInterfaceConfigAll(t, dut, batch, params) + batch.Set(t, dut) + + // Create sample steams for each port. + ochStreams := make(map[string]*samplestream.SampleStream[*oc.Component]) + trStreams := make(map[string]*samplestream.SampleStream[*oc.Component]) + hwPortStreams := make(map[string]*samplestream.SampleStream[*oc.Component]) + interfaceStreams := make(map[string]*samplestream.SampleStream[*oc.Interface]) + for _, p := range dut.Ports() { + ochStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().Component(params.OpticalChannelNames[p.Name()]).State(), samplingInterval) + trStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().Component(params.TransceiverNames[p.Name()]).State(), samplingInterval) + hwPortStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().Component(params.HWPortNames[p.Name()]).State(), samplingInterval) + interfaceStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().Interface(p.Name()).State(), samplingInterval) + defer ochStreams[p.Name()].Close() + defer trStreams[p.Name()].Close() + defer hwPortStreams[p.Name()].Close() + defer interfaceStreams[p.Name()].Close() + } + + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + awaitRxPowerStats(t, p, params, oc.Interface_OperStatus_UP) + } + time.Sleep(3 * samplingInterval) // Wait extra time for telemetry to be updated. + for _, p := range dut.Ports() { + validateNextSample(t, p, params, oc.Interface_OperStatus_UP, interfaceStreams[p.Name()].Next(), hwPortStreams[p.Name()].Next(), trStreams[p.Name()].Next(), ochStreams[p.Name()].Next()) + } + + t.Log("\n*** Bringing DOWN all interfaces\n\n\n") + for _, p := range dut.Ports() { + params.Enabled = false + cfgplugins.ToggleInterfaceState(t, p, params) + } + + // Wait for streaming telemetry to report the channels as down and validate stats updated. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + awaitRxPowerStats(t, p, params, oc.Interface_OperStatus_DOWN) + } + time.Sleep(3 * samplingInterval) // Wait extra time for telemetry to be updated. + for _, p := range dut.Ports() { + validateNextSample(t, p, params, oc.Interface_OperStatus_DOWN, interfaceStreams[p.Name()].Next(), hwPortStreams[p.Name()].Next(), trStreams[p.Name()].Next(), ochStreams[p.Name()].Next()) + } + + t.Logf("\n*** Bringing UP all interfaces\n\n\n") + for _, p := range dut.Ports() { + params.Enabled = true + cfgplugins.ToggleInterfaceState(t, p, params) + } + + // Wait for streaming telemetry to report the channels as up and validate stats updated. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + awaitRxPowerStats(t, p, params, oc.Interface_OperStatus_UP) + } + time.Sleep(3 * samplingInterval) // Wait extra time for telemetry to be updated. + for _, p := range dut.Ports() { + validateNextSample(t, p, params, oc.Interface_OperStatus_UP, interfaceStreams[p.Name()].Next(), hwPortStreams[p.Name()].Next(), trStreams[p.Name()].Next(), ochStreams[p.Name()].Next()) + } + } + } + } +} + +// setToDefaults sets the flags to their default values if the flags are not set. +func setToDefaults(t *testing.T, dut *ondatra.DUTDevice) { + if len(operationalModeList) == 0 { + operationalModeList = operationalModeList.Default(t, dut) + } + if len(frequencyList) == 0 { + frequencyList = frequencyList.Default(t, dut) + } + if len(targetOpticalPowerList) == 0 { + targetOpticalPowerList = targetOpticalPowerList.Default(t, dut) + } +} + +// awaitRxPowerStats waits for the Rx power stats to be within the expected range. +func awaitRxPowerStats(t *testing.T, p *ondatra.Port, params *cfgplugins.ConfigParameters, operStatus oc.E_Interface_OperStatus) { + switch operStatus { + case oc.Interface_OperStatus_UP: + _, ok := gnmi.Watch(t, p.Device(), gnmi.OC().Component(params.TransceiverNames[p.Name()]).Transceiver().Channel(0).InputPower().State(), timeout, func(rxP *ygnmi.Value[*oc.Component_Transceiver_Channel_InputPower]) bool { + rxPValue, present := rxP.Val() + return present && + rxPValue.GetMax() <= (params.TargetOpticalPower+powerLoss+powerReadingError) && rxPValue.GetMax() >= (params.TargetOpticalPower-powerLoss) && + rxPValue.GetMin() <= (params.TargetOpticalPower+powerLoss+powerReadingError) && rxPValue.GetMin() >= (params.TargetOpticalPower-powerLoss) && + rxPValue.GetAvg() <= (params.TargetOpticalPower+powerLoss+powerReadingError) && rxPValue.GetAvg() >= (params.TargetOpticalPower-powerLoss) && + rxPValue.GetInstant() <= (params.TargetOpticalPower+powerLoss+powerReadingError) && rxPValue.GetInstant() >= (params.TargetOpticalPower-powerLoss) + }).Await(t) + if !ok { + t.Fatalf("Rx power stats are not as expected for %v after %v minutes.", p.Name(), timeout.Minutes()) + } + case oc.Interface_OperStatus_DOWN: + _, ok := gnmi.Watch(t, p.Device(), gnmi.OC().Component(params.TransceiverNames[p.Name()]).Transceiver().Channel(0).InputPower().State(), timeout, func(rxP *ygnmi.Value[*oc.Component_Transceiver_Channel_InputPower]) bool { + rxPValue, ok := rxP.Val() + return ok && rxPValue.GetMax() <= inactivePower && rxPValue.GetMin() <= inactivePower && rxPValue.GetAvg() <= inactivePower && rxPValue.GetInstant() <= inactivePower + }).Await(t) + if !ok { + t.Fatalf("Rx power stats are not as expected for %v after %v minutes.", p.Name(), timeout.Minutes()) + } + default: + t.Fatalf("Unsupported oper status for %v: %v", p.PMD(), operStatus) + } +} + +// validateNextSample validates the stream data. +func validateNextSample(t *testing.T, p *ondatra.Port, params *cfgplugins.ConfigParameters, wantOperStatus oc.E_Interface_OperStatus, interfaceData *ygnmi.Value[*oc.Interface], hwPortData, transceiverData, opticalChannelData *ygnmi.Value[*oc.Component]) { + if interfaceData == nil { + t.Errorf("Data not received for port %v.", p.Name()) + return + } + interfaceValue, ok := interfaceData.Val() + if !ok { + t.Errorf("Channel data is empty for port %v.", p.Name()) + return + } + gotOperStatus := interfaceValue.GetOperStatus() + if gotOperStatus == oc.Interface_OperStatus_UNSET { + t.Errorf("Link state data is empty for port %v", p.Name()) + return + } + t.Run("Interface operStatus Validation", func(t *testing.T) { + t.Logf("\nInterface operStatus of %v is %v\n\n", p.Name(), gotOperStatus) + if diff := cmp.Diff(gotOperStatus, wantOperStatus); diff != "" { + t.Errorf("Interface operStatus is not as expected, diff (-got +want):\n%s", diff) + } + }) + if hwPortData == nil { + t.Errorf("HW Port data is empty for port %v.", p.Name()) + return + } + hwPortValue, ok := hwPortData.Val() + if !ok { + t.Errorf("HW Port Value is empty for port %v.", p.Name()) + return + } + validateHWPortTelemetry(t, p, params, hwPortValue) + if transceiverData == nil { + t.Errorf("Transceiver data is empty for port %v.", p.Name()) + return + } + transceiverValue, ok := transceiverData.Val() + if !ok { + t.Errorf("Transceiver Value is empty for port %v.", p.Name()) + return + } + validateTranscieverTelemetry(t, p, params, transceiverValue, gotOperStatus) + if opticalChannelData == nil { + t.Errorf("Optical Channel data is empty for port %v.", p.Name()) + return + } + opticalChannelValue, ok := opticalChannelData.Val() + if !ok { + t.Errorf("Optical Channel Value is empty for port %v.", p.Name()) + return + } + validateOpticalChannelTelemetry(t, p, params, opticalChannelValue, gotOperStatus) +} + +// validateHWPortTelemetry validates the hw port telemetry. +func validateHWPortTelemetry(t *testing.T, p *ondatra.Port, params *cfgplugins.ConfigParameters, hwPort *oc.Component) { + if p.PMD() == ondatra.PMD400GBASEZR || p.PMD() == ondatra.PMD400GBASEZRP { + // Skip HW Port validation for PMD400GBASEZR/PMD400GBASEZRP as it is not supported. + return + } + tcs := []testcase{ + { + desc: "HW Port Name Validation", + path: fmt.Sprintf(componentPath+"/state/name", params.HWPortNames[p.Name()]), + got: hwPort.GetName(), + want: params.HWPortNames[p.Name()], + }, + { + desc: "HW Port Location Validation", + path: fmt.Sprintf(componentPath+"/state/location", params.HWPortNames[p.Name()]), + got: hwPort.GetLocation(), + want: strings.Replace(params.TransceiverNames[p.Name()], "Ethernet", "", 1), + }, + { + desc: "HW Port Type Validation", + path: fmt.Sprintf(componentPath+"/state/type", params.HWPortNames[p.Name()]), + got: hwPort.GetType().(oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT), + want: oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_PORT, + }, + { + desc: "HW Port Breakout Index Validation", + path: fmt.Sprintf(componentPath+"/port/breakout-mode/groups/group[index=1]/state/index", params.HWPortNames[p.Name()]), + got: hwPort.GetPort().GetBreakoutMode().GetGroup(1).GetIndex(), + want: uint8(1), + }, + { + desc: "HW Port Breakout Speed Validation", + path: fmt.Sprintf(componentPath+"/port/breakout-mode/groups/group[index=1]/state/breakout-speed", params.HWPortNames[p.Name()]), + got: hwPort.GetPort().GetBreakoutMode().GetGroup(1).GetBreakoutSpeed(), + want: params.PortSpeed, + }, + { + desc: "HW Port Number of Breakouts Validation", + path: fmt.Sprintf(componentPath+"/port/breakout-mode/groups/group[index=1]/state/num-breakouts", params.HWPortNames[p.Name()]), + got: hwPort.GetPort().GetBreakoutMode().GetGroup(1).GetNumBreakouts(), + want: uint8(1), + }, + { + desc: "HW Port Number of Physical Channels Validation", + path: fmt.Sprintf(componentPath+"/port/breakout-mode/groups/group[index=1]/state/num-physical-channels", params.HWPortNames[p.Name()]), + got: hwPort.GetPort().GetBreakoutMode().GetGroup(1).GetNumPhysicalChannels(), + want: params.NumPhysicalChannels, + }, + } + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s of %v", tc.desc, p.Name()), func(t *testing.T) { + t.Logf("\n%s: %s = %v\n\n", p.Name(), tc.path, tc.got) + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("\n%s: %s, diff (-got +want):\n%s\n\n", p.Name(), tc.path, diff) + } + }) + } +} + +// validateTranscieverTelemetry validates the transceiver telemetry. +func validateTranscieverTelemetry(t *testing.T, p *ondatra.Port, params *cfgplugins.ConfigParameters, transceiver *oc.Component, operStatus oc.E_Interface_OperStatus) { + tcs := []testcase{ + { + desc: "Transceiver Name Validation", + path: fmt.Sprintf(componentPath+"/state/name", params.TransceiverNames[p.Name()]), + got: transceiver.GetName(), + want: params.TransceiverNames[p.Name()], + }, + { + desc: "Transceiver Parent Validation", + path: fmt.Sprintf(componentPath+"/state/parent", params.TransceiverNames[p.Name()]), + got: transceiver.GetParent(), + want: params.HWPortNames[p.Name()], + }, + { + desc: "Transceiver Location Validation", + path: fmt.Sprintf(componentPath+"/state/location", params.TransceiverNames[p.Name()]), + got: transceiver.GetLocation(), + want: strings.Replace(params.TransceiverNames[p.Name()], "Ethernet", "", 1), + }, + { + desc: "Transceiver removable Validation", + path: fmt.Sprintf(componentPath+"/state/removable", params.TransceiverNames[p.Name()]), + got: transceiver.GetRemovable(), + want: true, + }, + { + desc: "Transceiver Type Validation", + path: fmt.Sprintf(componentPath+"/state/type", params.TransceiverNames[p.Name()]), + got: transceiver.GetType().(oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT), + want: oc.PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT_TRANSCEIVER, + }, + { + desc: "Transceiver Oper-Status Validation", + path: fmt.Sprintf(componentPath+"/state/oper-status", params.TransceiverNames[p.Name()]), + got: transceiver.GetOperStatus(), + want: oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE, + }, + { + desc: "Transceiver Temperature Validation", + path: fmt.Sprintf(componentPath+"/state/temperature/instant", params.TransceiverNames[p.Name()]), + got: transceiver.GetTemperature().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedTemperature, + maxAllowed: maxAllowedTemperature, + }, + { + desc: "Transceiver Firmware Version Validation", + path: fmt.Sprintf(componentPath+"/state/firmware-version", params.TransceiverNames[p.Name()]), + got: transceiver.GetFirmwareVersion(), + patternToMatch: `.+`, + }, + { + desc: "Transceiver Hardware Version Validation", + path: fmt.Sprintf(componentPath+"/state/hardware-version", params.TransceiverNames[p.Name()]), + got: transceiver.GetHardwareVersion(), + patternToMatch: `.+`, + }, + { + desc: "Transceiver Serial Number Validation", + path: fmt.Sprintf(componentPath+"/state/serial-no", params.TransceiverNames[p.Name()]), + got: transceiver.GetSerialNo(), + patternToMatch: `.+`, + }, + { + desc: "Transceiver Part Number Validation", + path: fmt.Sprintf(componentPath+"/state/part-no", params.TransceiverNames[p.Name()]), + got: transceiver.GetPartNo(), + patternToMatch: `.+`, + }, + { + desc: "Transceiver mfg-name Validation", + path: fmt.Sprintf(componentPath+"/state/mfg-name", params.TransceiverNames[p.Name()]), + got: transceiver.GetMfgName(), + patternToMatch: `(CIENA|CISCO|LUMENTUM|NOKIA|INFINERA|ACACIA|MARVEL)`, + }, + { + desc: "Transceiver mfg-date Validation", + path: fmt.Sprintf(componentPath+"/state/mfg-date", params.TransceiverNames[p.Name()]), + got: transceiver.GetMfgDate(), + patternToMatch: `\d{4}-\d{2}-\d{2}`, + }, + { + desc: "Transceiver Form Factor Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/form-factor", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetFormFactor(), + want: params.FormFactor, + }, + { + desc: "Transceiver Present Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/present", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetPresent(), + want: oc.Transceiver_Present_PRESENT, + }, + { + desc: "Transceiver Connector Type Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/connector-type", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetConnectorType(), + want: oc.TransportTypes_FIBER_CONNECTOR_TYPE_LC_CONNECTOR, + }, + { + desc: "Transceiver Supply Voltage Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/supply-voltage/instant", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetSupplyVoltage().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedSupplyVoltage, + maxAllowed: maxAllowedSupplyVoltage, + }, + { + desc: "Transceiver Physical Channel Index Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/index", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetIndex(), + want: uint16(0), + }, + { + desc: "Transceiver Physical Channel Output Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/output-power/instant", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetOutputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerReadingError, + }, + { + desc: "Transceiver Physical Channel Instant Output Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/output-power/instant", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetOutputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Transceiver Physical Channel Instant Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/instant", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMin() - math.Abs(transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMin())*errorTolerance, + maxAllowed: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMax() + math.Abs(transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMax())*errorTolerance, + }, + { + desc: "Transceiver Physical Channel Instant Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/instant", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Transceiver Physical Channel Average Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/avg", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMin() - math.Abs(transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMin())*errorTolerance, + maxAllowed: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMax() + math.Abs(transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMax())*errorTolerance, + }, + { + desc: "Transceiver Physical Channel Average Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/avg", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Transceiver Physical Channel Minimum Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/min", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerLoss - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerLoss + powerReadingError, + }, + { + desc: "Transceiver Physical Channel Minimum Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/min", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Transceiver Physical Channel Maximum Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/max", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerLoss - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerLoss + powerReadingError, + }, + { + desc: "Transceiver Physical Channel Maximum Input Power Validation", + path: fmt.Sprintf(componentPath+"/transceiver/state/physical-channels/channel[index=0]/input-power/max", params.TransceiverNames[p.Name()]), + got: transceiver.GetTransceiver().GetChannel(0).GetInputPower().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + } + for _, tc := range tcs { + if tc.operStatus != oc.Interface_OperStatus_UNSET && tc.operStatus != operStatus { + // Skip the validation if the operStatus is not the same as the expected operStatus. + continue + } + t.Run(fmt.Sprintf("%s of %v", tc.desc, p.Name()), func(t *testing.T) { + t.Logf("\n%s: %s = %v\n\n", p.Name(), tc.path, tc.got) + switch { + case tc.patternToMatch != "": + val, ok := tc.got.(string) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want string\n\n", p.Name(), tc.path, tc.got) + } + if !regexp.MustCompile(tc.patternToMatch).MatchString(val) { + t.Errorf("\n%s: %s, invalid:\n got %v, want pattern %v\n\n", p.Name(), tc.path, tc.got, tc.patternToMatch) + } + case tc.operStatus == oc.Interface_OperStatus_UP: + val, ok := tc.got.(float64) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want float64\n\n", p.Name(), tc.path, tc.got) + } + if val < tc.minAllowed || val > tc.maxAllowed { + t.Errorf("\n%s: %s, out of range:\n got %v want >= %v, <= %v\n\n", p.Name(), tc.path, tc.got, tc.minAllowed, tc.maxAllowed) + } + case tc.operStatus == oc.Interface_OperStatus_DOWN: + val, ok := tc.got.(float64) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want float64\n\n", p.Name(), tc.path, tc.got) + } + if val > tc.maxAllowed { + t.Errorf("\n%s: %s, out of range:\n got %v want <= %v\n\n", p.Name(), tc.path, tc.got, tc.maxAllowed) + } + default: + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("\n%s: %s, diff (-got +want):\n%s\n\n", p.Name(), tc.path, diff) + } + } + }) + } +} + +// validateOpticalChannelTelemetry validates the optical channel telemetry. +func validateOpticalChannelTelemetry(t *testing.T, p *ondatra.Port, params *cfgplugins.ConfigParameters, opticalChannel *oc.Component, operStatus oc.E_Interface_OperStatus) { + tcs := []testcase{ + { + desc: "Optical Channel Name Validation", + path: fmt.Sprintf(componentPath+"/state/name", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetName(), + want: params.OpticalChannelNames[p.Name()], + }, + { + desc: "Optical Channel Parent Validation", + path: fmt.Sprintf(componentPath+"/state/parent", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetParent(), + want: params.TransceiverNames[p.Name()], + }, + { + desc: "Optical Channel Operational Mode Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/operational-mode", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOperationalMode(), + want: params.OperationalMode, + }, + { + desc: "Optical Channel Frequency Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/frequency", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetFrequency(), + want: params.Frequency, + }, + { + desc: "Optical Channel Target Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/target-output-power", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetTargetOutputPower(), + want: params.TargetOpticalPower, + }, + { + desc: "Optical Channel Instant Laser Bias Current Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/laser-bias-current/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetLaserBiasCurrent().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedLaserBiasCurrent, + maxAllowed: maxAllowedLaserBiasCurrent, + }, + { + desc: "Optical Channel Instant Laser Bias Current Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/laser-bias-current/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetLaserBiasCurrent().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveLaserBiasCurrent, + }, + { + desc: "Optical Channel Instant Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetInputPower().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetInputPower().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetInputPower().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetInputPower().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Instant Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Average Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetInputPower().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetInputPower().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetInputPower().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetInputPower().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Average Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Minimum Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerLoss - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerLoss + powerReadingError, + }, + { + desc: "Optical Channel Minimum Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Maximum Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerLoss - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerLoss + powerReadingError, + }, + { + desc: "Optical Channel Maximum Input Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/input-power/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetInputPower().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Instant Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetOutputPower().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetOutputPower().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetOutputPower().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetOutputPower().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Instant Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Average Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetOutputPower().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetOutputPower().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetOutputPower().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetOutputPower().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Average Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Minimum Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerReadingError, + }, + { + desc: "Optical Channel Minimum Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Maximum Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: params.TargetOpticalPower - powerReadingError, + maxAllowed: params.TargetOpticalPower + powerReadingError, + }, + { + desc: "Optical Channel Maximum Output Power Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/output-power/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetOutputPower().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactivePower, + }, + { + desc: "Optical Channel Instant Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Instant Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCDValue, + }, + { + desc: "Optical Channel Average Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Average Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCDValue, + }, + { + desc: "Optical Channel Minimum Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedCDValue, + maxAllowed: maxAllowedCDValue, + }, + { + desc: "Optical Channel Minimum Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCDValue, + }, + { + desc: "Optical Channel Maximum Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedCDValue, + maxAllowed: maxAllowedCDValue, + }, + { + desc: "Optical Channel Maximum Chromatic Dispersion Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/chromatic-dispersion/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetChromaticDispersion().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCDValue, + }, + { + desc: "Optical Channel Instant Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Instant Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/instant", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCarrierFrequencyOffset, + }, + { + desc: "Optical Channel Average Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMin() - math.Abs(opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMin())*errorTolerance, + maxAllowed: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMax() + math.Abs(opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMax())*errorTolerance, + }, + { + desc: "Optical Channel Average Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/avg", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCarrierFrequencyOffset, + }, + { + desc: "Optical Channel Minimum Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedCarrierFrequencyOffset, + maxAllowed: maxAllowedCarrierFrequencyOffset, + }, + { + desc: "Optical Channel Minimum Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/min", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCarrierFrequencyOffset, + }, + { + desc: "Optical Channel Maximum Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedCarrierFrequencyOffset, + maxAllowed: maxAllowedCarrierFrequencyOffset, + }, + { + desc: "Optical Channel Maximum Carrier Frequency Offset Validation", + path: fmt.Sprintf(componentPath+"/optical-channel/state/carrier-frequency-offset/max", params.OpticalChannelNames[p.Name()]), + got: opticalChannel.GetOpticalChannel().GetCarrierFrequencyOffset().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + maxAllowed: inactiveCarrierFrequencyOffset, + }, + } + for _, tc := range tcs { + if tc.operStatus != oc.Interface_OperStatus_UNSET && tc.operStatus != operStatus { + // Skip the validation if the operStatus is not the same as the expected operStatus. + continue + } + t.Run(fmt.Sprintf("%s of %v", tc.desc, p.Name()), func(t *testing.T) { + t.Logf("\n%s: %s = %v\n\n", p.Name(), tc.path, tc.got) + switch tc.operStatus { + case oc.Interface_OperStatus_UP: + val, ok := tc.got.(float64) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want float64\n\n", p.Name(), tc.path, tc.got) + } + if val < tc.minAllowed || val > tc.maxAllowed { + t.Errorf("\n%s: %s, out of range:\n got %v want >= %v, <= %v\n\n", p.Name(), tc.path, tc.got, tc.minAllowed, tc.maxAllowed) + } + case oc.Interface_OperStatus_DOWN: + val, ok := tc.got.(float64) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want float64\n\n", p.Name(), tc.path, tc.got) + } + if val > tc.maxAllowed { + t.Errorf("\n%s: %s, out of range:\n got %v want <= %v\n\n", p.Name(), tc.path, tc.got, tc.maxAllowed) + } + default: + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("\nOptical Channel Component: %s, diff (-got +want):\n%s\n\n", tc.desc, diff) + } + } + }) + } +} diff --git a/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/README.md b/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/README.md new file mode 100644 index 00000000000..cb797ddb0bf --- /dev/null +++ b/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/README.md @@ -0,0 +1,270 @@ +# TRANSCEIVER-102: Telemetry: ZR terminal-device OC paths streaming. + +## Summary + +Validate ZR optics module reports telemetry data for all leaves in + +```yaml + /terminal-device/logical-channels/channel/: +``` + +## Procedure + +* Connect two ZR interfaces using a duplex LC fiber jumper such that TX + output power of one is the RX input power of the other module. + +* To establish a point to point ZR link ensure the following: + * Both transceivers state is enabled + * Both transceivers are set to a valid operational mode + example 1. + * Both transceivers are set to a valid target TX output power + example -7 dBm. + * Both transceivers are tuned to a valid centre frequency + example 196.1 THz. + +* With the ZR link established as explained above, wait until + both interfaces oper-status are UP and all the min/avg/max values are + populated. Then verify that the following ZR transceiver telemetry paths + exist and are streamed valid values for both ZR optics. + +* Emulate flaps with the following procedure: + * Enable a pair of ZR interfaces on the DUT as explained above. + * Disable interface and wait at least one sample interval. + * Enable interface. + +* Verify that all the static leaves (e.g., breakout-speed, part-no, ...) + are present and reports valid strings or enums before, during and after flap. + +* Verify all the other leaves reports valid value of decimal64 before, + during and after flap. For leaves with stats, ensure min <= avg/instant <= max. + + * terminal-device/logical-channels/channel/state/index + * terminal-device/logical-channels/channel/state/description + * terminal-device/logical-channels/channel/state/logical-channel-type + * terminal-device/logical-channels/channel/state/loopback-mode + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/index + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/optical-channel + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/description + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/allocation + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/assignment-type + * terminal-device/logical-channels/channel/otn/state/q-value/instant + * terminal-device/logical-channels/channel/otn/state/q-value/avg + * terminal-device/logical-channels/channel/otn/state/q-value/min + * terminal-device/logical-channels/channel/otn/state/q-value/max + * terminal-device/logical-channels/channel/otn/state/esnr/instant + * terminal-device/logical-channels/channel/otn/state/esnr/avg + * terminal-device/logical-channels/channel/otn/state/esnr/min + * terminal-device/logical-channels/channel/otn/state/esnr/max + * terminal-device/logical-channels/channel/otn/state/pre-fec-ber/instant + * terminal-device/logical-channels/channel/otn/state/pre-fec-ber/avg + * terminal-device/logical-channels/channel/otn/state/pre-fec-ber/min + * terminal-device/logical-channels/channel/otn/state/pre-fec-ber/max + * terminal-device/logical-channels/channel/otn/state/fec-uncorrectable-blocks + * terminal-device/logical-channels/channel/state/index + * terminal-device/logical-channels/channel/state/description + * terminal-device/logical-channels/channel/state/logical-channel-type + * terminal-device/logical-channels/channel/state/loopback-mode + * terminal-device/logical-channels/channel/ingress/state/interface + * terminal-device/logical-channels/channel/ingress/state/transceiver + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/index + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/logical-channel + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/allocation + * terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/assignment-type + +**Note:** For min, max, and avg values, 10 second sampling is preferred. If + 10 seconds is not supported, the sampling interval used must be + specified by adding a deviation to the test. + + +### Canonical OC +```json +{ + "openconfig-interfaces:interfaces": { + "interface": [ + { + "config": { + "name": "Ethernet4/1/1", + "type": "ethernetCsmacd" + }, + "name": "Ethernet4/1/1", + "openconfig-if-ethernet:ethernet": { + "config": { + "duplex-mode": "FULL", + "port-speed": "SPEED_800GB" + } + } + } + ] + }, + "openconfig-platform:components": { + "component": [ + { + "config": { + "name": "Ethernet4/1-Port" + }, + "name": "Ethernet4/1-Port", + "openconfig-platform-port:port": { + "breakout-mode": { + "groups": { + "group": [ + { + "config": { + "breakout-speed": "openconfig-if-ethernet:SPEED_800GB", + "index": 1, + "num-breakouts": 1, + "num-physical-channels": 8 + }, + "index": 1 + } + ] + } + } + } + }, + { + "config": { + "name": "Ethernet4/1" + }, + "name": "Ethernet4/1" + }, + { + "config": { + "name": "Ethernet4/1-Optical0" + }, + "name": "Ethernet4/1-Optical0", + "openconfig-terminal-device:optical-channel": { + "config": { + "operational-mode": 1, + "frequency": "196000000", + "target-output-power": "-7" + } + } + } + ] + }, + "openconfig-terminal-device:terminal-device": { + "logical-channels": { + "channel": [ + { + "logical-channel-assignments": { + "assignment": [ + { + "config": { + "allocation": "800", + "assignment-type": "OPTICAL_CHANNEL", + "description": "OTN to optical channel assignment", + "index": 1, + "optical-channel": "Ethernet4/1-Optical0" + } + } + ] + }, + "config": { + "admin-state": "ENABLED", + "description": "OTN Logical Channel", + "index": 8000, + "logical-channel-type": "openconfig-transport-types:PROT_OTN" + } + }, + { + "ingress": { + "config": { + "interface": "Ethernet4/1/1", + "transceiver": "Ethernet4/1" + } + }, + "logical-channel-assignments": { + "assignment": [ + { + "config": { + "allocation": "800", + "assignment-type": "LOGICAL_CHANNEL", + "description": "ETH to OTN assignment", + "index": 1, + "logical-channel": 8000 + } + } + ] + }, + "config": { + "admin-state": "ENABLED", + "description": "ETH Logical Channel", + "index": 80000, + "logical-channel-type": "openconfig-transport-types:PROT_ETHERNET", + "rate-class": "openconfig-transport-types:TRIB_RATE_800G", + "trib-protocol": "openconfig-transport-types:PROT_800GE" + } + } + ] + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config Parameter coverage + /interfaces/interface/config/enabled: + /interfaces/interface/config/type: + /interfaces/interface/ethernet/config/port-speed: + /interfaces/interface/ethernet/config/duplex-mode: + /components/component/port/breakout-mode/groups/group/config/breakout-speed: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/config/num-breakouts: + platform_type: ["PORT"] + /components/component/port/breakout-mode/groups/group/config/num-physical-channels: + platform_type: ["PORT"] + /components/component/optical-channel/config/operational-mode: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/config/frequency: + platform_type: ["OPTICAL_CHANNEL"] + /components/component/optical-channel/config/target-output-power: + platform_type: ["OPTICAL_CHANNEL"] + /terminal-device/logical-channels/channel/config/admin-state: + /terminal-device/logical-channels/channel/config/description: + /terminal-device/logical-channels/channel/config/logical-channel-type: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/description: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/assignment-type: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/allocation: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/optical-channel: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/config/logical-channel: + /terminal-device/logical-channels/channel/ingress/config/transceiver: + /terminal-device/logical-channels/channel/ingress/config/interface: + + # Telemetry Parameter coverage + /terminal-device/logical-channels/channel/state/index: + /terminal-device/logical-channels/channel/state/description: + /terminal-device/logical-channels/channel/state/logical-channel-type: + /terminal-device/logical-channels/channel/state/loopback-mode: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/index: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/description: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/allocation: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/assignment-type: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/optical-channel: + /terminal-device/logical-channels/channel/otn/state/q-value/instant: + /terminal-device/logical-channels/channel/otn/state/q-value/avg: + /terminal-device/logical-channels/channel/otn/state/q-value/min: + /terminal-device/logical-channels/channel/otn/state/q-value/max: + /terminal-device/logical-channels/channel/otn/state/esnr/instant: + /terminal-device/logical-channels/channel/otn/state/esnr/avg: + /terminal-device/logical-channels/channel/otn/state/esnr/min: + /terminal-device/logical-channels/channel/otn/state/esnr/max: + /terminal-device/logical-channels/channel/otn/state/pre-fec-ber/instant: + /terminal-device/logical-channels/channel/otn/state/pre-fec-ber/avg: + /terminal-device/logical-channels/channel/otn/state/pre-fec-ber/min: + /terminal-device/logical-channels/channel/otn/state/pre-fec-ber/max: + /terminal-device/logical-channels/channel/otn/state/fec-uncorrectable-blocks: + /terminal-device/logical-channels/channel/ingress/state/interface: + /terminal-device/logical-channels/channel/ingress/state/transceiver: + /terminal-device/logical-channels/channel/logical-channel-assignments/assignment/state/logical-channel: + +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: + gNMI.Subscribe: +``` + + diff --git a/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/metadata.textproto b/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/metadata.textproto new file mode 100644 index 00000000000..263ce235cbb --- /dev/null +++ b/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/metadata.textproto @@ -0,0 +1,19 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "ed6b4f14-d5bb-4ee1-bff8-cc9f84aeb955" +plan_id: "TRANSCEIVER-102" +description: "Telemetry: ZR terminal-device OC paths streaming." +testbed: TESTBED_DUT_400ZR +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + default_network_instance: "default" + channel_assignment_rate_class_parameters_unsupported: true + missing_port_to_optical_channel_component_mapping: true + otn_channel_trib_unsupported: true + otn_to_eth_assignment: true + } +} \ No newline at end of file diff --git a/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/zr_800_terminal_device_paths_test.go b/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/zr_800_terminal_device_paths_test.go new file mode 100644 index 00000000000..f350b3609cc --- /dev/null +++ b/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/zr_800_terminal_device_paths_test.go @@ -0,0 +1,692 @@ +package zr_800_terminal_device_paths_test + +import ( + "flag" + "fmt" + "slices" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/components" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/samplestream" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + samplingInterval = 1 * time.Second + minAllowedQValue = 7.0 + maxAllowedQValue = 14.0 + minAllowedPreFECBER = 1e-9 + maxAllowedPreFECBER = 1e-2 + minAllowedESNR = 10.0 + maxAllowedESNR = 25.0 + inactiveQValue = 0.0 + inactiveESNR = 0.0 + errorTolerance = 0.05 + timeout = 10 * time.Minute +) + +var ( + frequencyList cfgplugins.FrequencyList + targetOpticalPowerList cfgplugins.TargetOpticalPowerList + operationalModeList cfgplugins.OperationalModeList + logicalChannelPath = "openconfig/terminal-device/logical-channels/channel[index=%v]" + // Different acceptable values for inactive pre-FEC BER. + // Cisco returns 0.5 for inactive pre-FEC BER. + // Arista MVC800 returns 1.0 for inactive pre-FEC BER. + // All other vendors/platforms return 0.0 for inactive pre-FEC BER. + inactivePreFECBER = []float64{0.0, 0.5, 1.0} +) + +type testcase struct { + desc string + path string + got any + want any + oneOf []float64 + operStatus oc.E_Interface_OperStatus + minAllowed float64 + maxAllowed float64 +} + +func init() { + flag.Var(&operationalModeList, "operational_mode", "operational-mode for the channel.") + flag.Var(&frequencyList, "frequency", "frequency for the channel.") + flag.Var(&targetOpticalPowerList, "target_optical_power", "target-optical-power for the channel.") +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestTerminalDevicePaths(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + fptest.ConfigureDefaultNetworkInstance(t, dut) + + setToDefaults(t, dut) + + for _, operationalMode := range operationalModeList { + for _, frequency := range frequencyList { + for _, targetOpticalPower := range targetOpticalPowerList { + + t.Logf("\n*** Configure interfaces with Operational Mode: %v, Optical Frequency: %v, Target Power: %v\n\n\n", operationalMode, frequency, targetOpticalPower) + params := &cfgplugins.ConfigParameters{ + Enabled: true, + Frequency: frequency, + TargetOpticalPower: targetOpticalPower, + OperationalMode: operationalMode, + } + batch := &gnmi.SetBatch{} + cfgplugins.NewInterfaceConfigAll(t, dut, batch, params) + batch.Set(t, dut) + + // Create sample steams for each port. + ethStreams := make(map[string]*samplestream.SampleStream[*oc.TerminalDevice_Channel]) + otnStreams := make(map[string]*samplestream.SampleStream[*oc.TerminalDevice_Channel]) + interfaceStreams := make(map[string]*samplestream.SampleStream[*oc.Interface]) + for _, p := range dut.Ports() { + ethStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(params.ETHIndexes[p.Name()]).State(), samplingInterval) + otnStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(params.OTNIndexes[p.Name()]).State(), samplingInterval) + interfaceStreams[p.Name()] = samplestream.New(t, dut, gnmi.OC().Interface(p.Name()).State(), samplingInterval) + defer ethStreams[p.Name()].Close() + defer otnStreams[p.Name()].Close() + defer interfaceStreams[p.Name()].Close() + } + + // Wait for streaming telemetry to report the channels as up. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + awaitQValueStats(t, dut, p, params, oc.Interface_OperStatus_UP) + } + time.Sleep(3 * samplingInterval) // Wait extra time for telemetry to be updated. + for _, p := range dut.Ports() { + validateNextSample(t, dut, p, params, oc.Interface_OperStatus_UP, interfaceStreams[p.Name()].Next(), otnStreams[p.Name()].Next(), ethStreams[p.Name()].Next(), operationalMode) + } + + t.Logf("\n*** Bringing DOWN all interfaces\n\n\n") + for _, p := range dut.Ports() { + params.Enabled = false + cfgplugins.ToggleInterfaceState(t, p, params) + } + + // Wait for streaming telemetry to report the channels as down and validate stats updated. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_DOWN) + awaitQValueStats(t, dut, p, params, oc.Interface_OperStatus_DOWN) + } + time.Sleep(3 * samplingInterval) // Wait extra time for telemetry to be updated. + for _, p := range dut.Ports() { + validateNextSample(t, dut, p, params, oc.Interface_OperStatus_DOWN, interfaceStreams[p.Name()].Next(), otnStreams[p.Name()].Next(), ethStreams[p.Name()].Next(), operationalMode) + } + + t.Logf("\n*** Bringing UP all interfaces\n\n\n") + for _, p := range dut.Ports() { + params.Enabled = true + cfgplugins.ToggleInterfaceState(t, p, params) + } + + // Wait for streaming telemetry to report the channels as up and validate stats updated. + for _, p := range dut.Ports() { + gnmi.Await(t, dut, gnmi.OC().Interface(p.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) + awaitQValueStats(t, dut, p, params, oc.Interface_OperStatus_UP) + } + time.Sleep(3 * samplingInterval) // Wait extra time for telemetry to be updated. + for _, p := range dut.Ports() { + validateNextSample(t, dut, p, params, oc.Interface_OperStatus_UP, interfaceStreams[p.Name()].Next(), otnStreams[p.Name()].Next(), ethStreams[p.Name()].Next(), operationalMode) + } + } + } + } +} + +// setToDefaults sets the flags to their default values if the flags are not set. +func setToDefaults(t *testing.T, dut *ondatra.DUTDevice) { + if len(operationalModeList) == 0 { + operationalModeList = operationalModeList.Default(t, dut) + } + if len(frequencyList) == 0 { + frequencyList = frequencyList.Default(t, dut) + } + if len(targetOpticalPowerList) == 0 { + targetOpticalPowerList = targetOpticalPowerList.Default(t, dut) + } +} + +// awaitQValueStats waits for the QValue stats (i.e., min/max/avg) to be within the expected range. +func awaitQValueStats(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port, params *cfgplugins.ConfigParameters, operStatus oc.E_Interface_OperStatus) { + switch operStatus { + case oc.Interface_OperStatus_UP: + _, ok := gnmi.Watch(t, dut, gnmi.OC().TerminalDevice().Channel(params.OTNIndexes[p.Name()]).Otn().QValue().State(), timeout, func(min *ygnmi.Value[*oc.TerminalDevice_Channel_Otn_QValue]) bool { + qValue, present := min.Val() + return present && + qValue.GetMin() <= maxAllowedQValue && qValue.GetMin() >= minAllowedQValue && + qValue.GetAvg() <= maxAllowedQValue && qValue.GetAvg() >= minAllowedQValue && + qValue.GetMax() <= maxAllowedQValue && qValue.GetMax() >= minAllowedQValue && + qValue.GetInstant() <= maxAllowedQValue && qValue.GetInstant() >= minAllowedQValue + }).Await(t) + if !ok { + t.Fatalf("QValue stats are not as expected for %v after %v minutes.", p.Name(), timeout.Minutes()) + } + case oc.Interface_OperStatus_DOWN: + _, ok := gnmi.Watch(t, dut, gnmi.OC().TerminalDevice().Channel(params.OTNIndexes[p.Name()]).Otn().QValue().State(), timeout, func(max *ygnmi.Value[*oc.TerminalDevice_Channel_Otn_QValue]) bool { + qValue, present := max.Val() + return present && qValue.GetMin() == inactiveQValue && qValue.GetAvg() == inactiveQValue && qValue.GetMax() == inactiveQValue && qValue.GetInstant() == inactiveQValue + }).Await(t) + if !ok { + t.Fatalf("QValue stats are not as expected for %v after %v minutes.", p.Name(), timeout.Minutes()) + } + default: + t.Fatalf("Unsupported oper status for %v: %v", p.Name(), operStatus) + } +} + +// validateNextSample validates the stream data. +func validateNextSample(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port, params *cfgplugins.ConfigParameters, wantOperStatus oc.E_Interface_OperStatus, interfaceData *ygnmi.Value[*oc.Interface], otnChannelData, ethChannelData *ygnmi.Value[*oc.TerminalDevice_Channel], operationalMode uint16) { + // Validate Interface OperStatus Telemetry. + if interfaceData == nil { + t.Errorf("Data not received for %v.", p.Name()) + return + } + interfaceValue, ok := interfaceData.Val() + if !ok { + t.Errorf("Channel data is empty for %v.", p.Name()) + return + } + gotOperStatus := interfaceValue.GetOperStatus() + if gotOperStatus == oc.Interface_OperStatus_UNSET { + t.Errorf("Link state data is empty for %v", p.Name()) + return + } + t.Run("Interface operStatus Validation", func(t *testing.T) { + t.Logf("\nInterface operStatus of %v is %v\n\n", p.Name(), gotOperStatus) + if diff := cmp.Diff(gotOperStatus, wantOperStatus); diff != "" { + t.Errorf("Interface operStatus is not as expected, diff (-got +want):\n%s", diff) + } + }) + + // Validate OTN Channel Telemetry. + if otnChannelData == nil { + t.Errorf("OTN Channel data is empty for %v.", p.Name()) + return + } + otnChannelValue, ok := otnChannelData.Val() + if !ok { + t.Errorf("OTN Channel value is empty for %v.", p.Name()) + return + } + validateOTNChannelTelemetry(t, dut, p, params, otnChannelValue, gotOperStatus) + + // Validate Ethernet Channel Telemetry. + if ethChannelData == nil { + t.Errorf("Ethernet Channel data is empty for %v.", p.Name()) + return + } + ethChannelValue, ok := ethChannelData.Val() + if !ok { + t.Errorf("Ethernet Channel value is empty for %v.", p.Name()) + return + } + validateEthernetChannelTelemetry(t, dut, p, params, ethChannelValue) +} + +// validateOTNChannelTelemetry validates the OTN channel telemetry. +func validateOTNChannelTelemetry(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port, params *cfgplugins.ConfigParameters, otnChannel *oc.TerminalDevice_Channel, operStatus oc.E_Interface_OperStatus) { + var firstAssignmentIndex uint32 + if deviations.OTNChannelAssignmentCiscoNumbering(dut) { + firstAssignmentIndex = 1 + } else { + firstAssignmentIndex = 0 + } + tcs := []testcase{ + { + desc: "OTN Index Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/index", params.OTNIndexes[p.Name()]), + got: otnChannel.GetIndex(), + want: params.OTNIndexes[p.Name()], + }, + { + desc: "OTN Description Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/description", params.OTNIndexes[p.Name()]), + got: otnChannel.GetDescription(), + want: "OTN Logical Channel", + }, + { + desc: "OTN Logical Channel Type Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/logical-channel-type", params.OTNIndexes[p.Name()]), + got: otnChannel.GetLogicalChannelType(), + want: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN, + }, + { + desc: "OTN Loopback Mode Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/loopback-mode", params.OTNIndexes[p.Name()]), + got: otnChannel.GetLoopbackMode(), + want: oc.TerminalDevice_LoopbackModeType_NONE, + }, + { + desc: "OTN to Optical Channel Assignment Index Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/index", params.OTNIndexes[p.Name()], firstAssignmentIndex), + got: otnChannel.GetAssignment(firstAssignmentIndex).GetIndex(), + want: uint32(firstAssignmentIndex), + }, + { + desc: "OTN to Optical Channel Assignment Optical Channel Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/optical-channel", params.OTNIndexes[p.Name()], firstAssignmentIndex), + got: otnChannel.GetAssignment(firstAssignmentIndex).GetOpticalChannel(), + want: components.OpticalChannelComponentFromPort(t, dut, p), + }, + { + desc: "OTN to Optical Channel Assignment Description Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/description", params.OTNIndexes[p.Name()], firstAssignmentIndex), + got: otnChannel.GetAssignment(firstAssignmentIndex).GetDescription(), + want: "OTN to Optical Channel", + }, + { + desc: "OTN to Optical Channel Assignment Allocation Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/allocation", params.OTNIndexes[p.Name()], firstAssignmentIndex), + got: otnChannel.GetAssignment(firstAssignmentIndex).GetAllocation(), + want: params.Allocation, + }, + { + desc: "OTN to Optical Channel Assignment Type Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/assignment-type", params.OTNIndexes[p.Name()], firstAssignmentIndex), + got: otnChannel.GetAssignment(firstAssignmentIndex).GetAssignmentType(), + want: oc.Assignment_AssignmentType_OPTICAL_CHANNEL, + }, + { + desc: "Instant QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/instant", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: otnChannel.GetOtn().GetQValue().GetMin() * (1 - errorTolerance), + maxAllowed: otnChannel.GetOtn().GetQValue().GetMax() * (1 + errorTolerance), + }, + { + desc: "Instant QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/instant", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveQValue, + }, + { + desc: "Average QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/avg", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: otnChannel.GetOtn().GetQValue().GetMin() * (1 - errorTolerance), + maxAllowed: otnChannel.GetOtn().GetQValue().GetMax() * (1 + errorTolerance), + }, + { + desc: "Average QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/avg", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveQValue, + }, + { + desc: "Minimum QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/min", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedQValue, + maxAllowed: maxAllowedQValue, + }, + { + desc: "Minimum QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/min", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveQValue, + }, + { + desc: "Maximum QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/max", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedQValue, + maxAllowed: maxAllowedQValue, + }, + { + desc: "Maximum QValue Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/q-value/max", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetQValue().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveQValue, + }, + { + desc: "Instant ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/instant", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: otnChannel.GetOtn().GetEsnr().GetMin() * (1 - errorTolerance), + maxAllowed: otnChannel.GetOtn().GetEsnr().GetMax() * (1 + errorTolerance), + }, + { + desc: "Instant ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/instant", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveESNR, + }, + { + desc: "Average ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/avg", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: otnChannel.GetOtn().GetEsnr().GetMin() * (1 - errorTolerance), + maxAllowed: otnChannel.GetOtn().GetEsnr().GetMax() * (1 + errorTolerance), + }, + { + desc: "Average ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/avg", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveESNR, + }, + { + desc: "Minimum ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/min", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedESNR, + maxAllowed: maxAllowedESNR, + }, + { + desc: "Minimum ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/min", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveESNR, + }, + { + desc: "Maximum ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/max", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedESNR, + maxAllowed: maxAllowedESNR, + }, + { + desc: "Maximum ESNR Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/esnr/max", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetEsnr().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + want: inactiveESNR, + }, + { + desc: "Instant PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/instant", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetInstant(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: otnChannel.GetOtn().GetPreFecBer().GetMin() * (1 - errorTolerance), + maxAllowed: otnChannel.GetOtn().GetPreFecBer().GetMax() * (1 + errorTolerance), + }, + { + desc: "Instant PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/instant", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetInstant(), + operStatus: oc.Interface_OperStatus_DOWN, + oneOf: inactivePreFECBER, + }, + { + desc: "Average PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/avg", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetAvg(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: otnChannel.GetOtn().GetPreFecBer().GetMin() * (1 - errorTolerance), + maxAllowed: otnChannel.GetOtn().GetPreFecBer().GetMax() * (1 + errorTolerance), + }, + { + desc: "Average PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/avg", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetAvg(), + operStatus: oc.Interface_OperStatus_DOWN, + oneOf: inactivePreFECBER, + }, + { + desc: "Minimum PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/min", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetMin(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedPreFECBER, + maxAllowed: maxAllowedPreFECBER, + }, + { + desc: "Minimum PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/min", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetMin(), + operStatus: oc.Interface_OperStatus_DOWN, + oneOf: inactivePreFECBER, + }, + { + desc: "Maximum PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/max", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetMax(), + operStatus: oc.Interface_OperStatus_UP, + minAllowed: minAllowedPreFECBER, + maxAllowed: maxAllowedPreFECBER, + }, + { + desc: "Maximum PreFECBER Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/pre-fec-ber/max", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetPreFecBer().GetMax(), + operStatus: oc.Interface_OperStatus_DOWN, + oneOf: inactivePreFECBER, + }, + { + desc: "FEC Uncorrectable Block Count Validation", + path: fmt.Sprintf(logicalChannelPath+"/otn/state/fec-uncorrectable-blocks", params.OTNIndexes[p.Name()]), + got: otnChannel.GetOtn().GetFecUncorrectableBlocks(), + operStatus: oc.Interface_OperStatus_UP, + want: uint64(0), + }, + } + if deviations.OTNToETHAssignment(dut) { + tcs = append(tcs, []testcase{ + { + desc: "OTN to Logical Channel Assignment Index Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/index", params.OTNIndexes[p.Name()], firstAssignmentIndex+1), + got: otnChannel.GetAssignment(firstAssignmentIndex + 1).GetIndex(), + want: uint32(firstAssignmentIndex + 1), + }, + { + desc: "OTN to Logical Channel Assignment Optical Channel Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/logical-channel", params.OTNIndexes[p.Name()], firstAssignmentIndex+1), + got: otnChannel.GetAssignment(firstAssignmentIndex + 1).GetLogicalChannel(), + want: params.ETHIndexes[p.Name()], + }, + { + desc: "OTN to Logical Channel Assignment Description Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/description", params.OTNIndexes[p.Name()], firstAssignmentIndex+1), + got: otnChannel.GetAssignment(firstAssignmentIndex + 1).GetDescription(), + want: "OTN to ETH", + }, + { + desc: "OTN to Logical Channel Assignment Allocation Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/allocation", params.OTNIndexes[p.Name()], firstAssignmentIndex+1), + got: otnChannel.GetAssignment(firstAssignmentIndex + 1).GetAllocation(), + want: params.Allocation, + }, + { + desc: "OTN to Logical Channel Assignment Type Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/assignment-type", params.OTNIndexes[p.Name()], firstAssignmentIndex+1), + got: otnChannel.GetAssignment(firstAssignmentIndex + 1).GetAssignmentType(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + }...) + } + if !deviations.ChannelRateClassParametersUnsupported(dut) { + tcs = append(tcs, testcase{ + desc: "Rate Class", + path: fmt.Sprintf(logicalChannelPath+"/state/rate-class", params.OTNIndexes[p.Name()]), + got: otnChannel.GetRateClass(), + want: params.RateClass, + }) + } + if !deviations.OTNChannelTribUnsupported(dut) { + tcs = append(tcs, []testcase{ + { + desc: "OTN Trib Protocol Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/trib-protocol", params.OTNIndexes[p.Name()]), + got: otnChannel.GetTribProtocol(), + want: params.TribProtocol, + }, + { + desc: "OTN Admin State Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/admin-state", params.OTNIndexes[p.Name()]), + got: otnChannel.GetAdminState(), + want: oc.TerminalDevice_AdminStateType_ENABLED, + }, + }...) + } + for _, tc := range tcs { + if tc.operStatus != oc.Interface_OperStatus_UNSET && tc.operStatus != operStatus { + // Skip the validation if the operStatus is not the same as the expected operStatus. + continue + } + t.Run(fmt.Sprintf("%s of %v", tc.desc, p.Name()), func(t *testing.T) { + t.Logf("\n%s: %s = %v\n\n", p.Name(), tc.path, tc.got) + switch { + case len(tc.oneOf) > 0: + val, ok := tc.got.(float64) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want float64\n\n", p.Name(), tc.path, tc.got) + } + if !slices.Contains(tc.oneOf, val) { + t.Errorf("\n%s: %s, none of the expected values: \n got %v want one of %v\n\n", p.Name(), tc.path, tc.got, tc.oneOf) + } + case tc.operStatus == oc.Interface_OperStatus_UP && tc.want != nil: + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("\n%s: %s, diff (-got +want):\n%s\n\n", p.Name(), tc.path, diff) + } + case tc.operStatus == oc.Interface_OperStatus_UP: + val, ok := tc.got.(float64) + if !ok { + t.Errorf("\n%s: %s, invalid type: \n got %v want float64\n\n", p.Name(), tc.path, tc.got) + } + if val < tc.minAllowed || val > tc.maxAllowed { + t.Errorf("\n%s: %s, out of range:\n got %v want >= %v, <= %v\n\n", p.Name(), tc.path, tc.got, tc.minAllowed, tc.maxAllowed) + } + default: + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("\n%s: %s, diff (-got +want):\n%s\n\n", p.Name(), tc.path, diff) + } + } + }) + } +} + +// validateEthernetChannelTelemetry validates the ethernet channel telemetry. +func validateEthernetChannelTelemetry(t *testing.T, dut *ondatra.DUTDevice, p *ondatra.Port, params *cfgplugins.ConfigParameters, ethChannel *oc.TerminalDevice_Channel) { + var assignmentIndex uint32 + if deviations.EthChannelAssignmentCiscoNumbering(dut) { + assignmentIndex = 1 + } else { + assignmentIndex = 0 + } + tcs := []testcase{ + { + desc: "ETH Index Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/index", params.ETHIndexes[p.Name()]), + got: ethChannel.GetIndex(), + want: params.ETHIndexes[p.Name()], + }, + { + desc: "ETH Description Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/description", params.ETHIndexes[p.Name()]), + got: ethChannel.GetDescription(), + want: "ETH Logical Channel", + }, + { + desc: "ETH Logical Channel Type Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/logical-channel-type", params.ETHIndexes[p.Name()]), + got: ethChannel.GetLogicalChannelType(), + want: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_ETHERNET, + }, + { + desc: "ETH Loopback Mode Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/loopback-mode", params.ETHIndexes[p.Name()]), + got: ethChannel.GetLoopbackMode(), + want: oc.TerminalDevice_LoopbackModeType_NONE, + }, + { + desc: "ETH Assignment Index Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/index", params.ETHIndexes[p.Name()], assignmentIndex), + got: ethChannel.GetAssignment(assignmentIndex).GetIndex(), + want: uint32(assignmentIndex), + }, + { + desc: "ETH Assignment Logical Channel Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/logical-channel", params.ETHIndexes[p.Name()], assignmentIndex), + got: ethChannel.GetAssignment(assignmentIndex).GetLogicalChannel(), + want: params.OTNIndexes[p.Name()], + }, + { + desc: "ETH Assignment Allocation Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/allocation", params.ETHIndexes[p.Name()], assignmentIndex), + got: ethChannel.GetAssignment(assignmentIndex).GetAllocation(), + want: params.Allocation, + }, + { + desc: "ETH Assignment Type Validation", + path: fmt.Sprintf(logicalChannelPath+"/logical-channel-assignments/assignment[index=%v]/state/assignment-type", params.ETHIndexes[p.Name()], assignmentIndex), + got: ethChannel.GetAssignment(assignmentIndex).GetAssignmentType(), + want: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + } + if !deviations.ChannelRateClassParametersUnsupported(dut) { + tcs = append(tcs, testcase{ + desc: "ETH Rate Class Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/rate-class", params.ETHIndexes[p.Name()]), + got: ethChannel.GetRateClass(), + want: params.RateClass, + }) + } + if !deviations.OTNChannelTribUnsupported(dut) { + tcs = append(tcs, []testcase{ + { + desc: "ETH Trib Protocol Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/trib-protocol", params.ETHIndexes[p.Name()]), + got: ethChannel.GetTribProtocol(), + want: params.TribProtocol, + }, + { + desc: "ETH Admin State Validation", + path: fmt.Sprintf(logicalChannelPath+"/state/admin-state", params.ETHIndexes[p.Name()]), + got: ethChannel.GetAdminState(), + want: oc.TerminalDevice_AdminStateType_ENABLED, + }, + }...) + } + if !deviations.EthChannelIngressParametersUnsupported(dut) { + tcs = append(tcs, []testcase{ + { + desc: "ETH Ingress Interface Validation", + path: fmt.Sprintf(logicalChannelPath+"/ingress/state/interface", params.ETHIndexes[p.Name()]), + got: ethChannel.GetIngress().GetInterface(), + want: p.Name(), + }, + { + desc: "ETH Ingress Transceiver Validation", + path: fmt.Sprintf(logicalChannelPath+"/ingress/state/transceiver", params.ETHIndexes[p.Name()]), + got: ethChannel.GetIngress().GetTransceiver(), + want: gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()), + }, + }...) + } + for _, tc := range tcs { + t.Run(fmt.Sprintf("%s of %v", tc.desc, p.Name()), func(t *testing.T) { + t.Logf("\n%s: %s = %v\n\n", p.Name(), tc.path, tc.got) + if diff := cmp.Diff(tc.got, tc.want); diff != "" { + t.Errorf("\n%s: %s, diff (-got +want):\n%s\n\n", p.Name(), tc.path, diff) + } + }) + } +} diff --git a/feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/README.md b/feature/platform/transceiver/tests/zr_cd_test/README.md similarity index 100% rename from feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/README.md rename to feature/platform/transceiver/tests/zr_cd_test/README.md diff --git a/feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/metadata.textproto b/feature/platform/transceiver/tests/zr_cd_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_cd_test/metadata.textproto diff --git a/feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/zr_cd_test.go b/feature/platform/transceiver/tests/zr_cd_test/zr_cd_test.go similarity index 98% rename from feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/zr_cd_test.go rename to feature/platform/transceiver/tests/zr_cd_test/zr_cd_test.go index 6ba9ec8b7c7..8846b6bb930 100644 --- a/feature/platform/transceiver/chromatic_dispersion/tests/zr_cd_test/zr_cd_test.go +++ b/feature/platform/transceiver/tests/zr_cd_test/zr_cd_test.go @@ -100,7 +100,7 @@ func TestCDValue(t *testing.T) { dp1 := dut.Port(t, "port1") dp2 := dut.Port(t, "port2") operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, dp1) cfgplugins.InterfaceConfig(t, dut, dp2) diff --git a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/README.md b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md similarity index 100% rename from feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/README.md rename to feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/README.md diff --git a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/metadata.textproto b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/metadata.textproto similarity index 88% rename from feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/metadata.textproto index f924d2bb920..820a8995ca2 100644 --- a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/metadata.textproto +++ b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/metadata.textproto @@ -36,3 +36,11 @@ platform_exceptions: { eth_channel_ingress_parameters_unsupported: true } } +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + non_interval_fec_error_counter: true + } +} diff --git a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go similarity index 67% rename from feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go rename to feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go index be6f14f4e4c..04e20810f99 100644 --- a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go +++ b/feature/platform/transceiver/tests/zr_fec_uncorrectable_frames_test/zr_fec_uncorrectable_frames_test.go @@ -22,6 +22,7 @@ import ( "time" "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/samplestream" "github.com/openconfig/ondatra" @@ -46,20 +47,28 @@ func TestMain(m *testing.M) { fptest.RunTests(m) } -func validateFecUncorrectableBlocks(t *testing.T, stream *samplestream.SampleStream[uint64]) { +func validateFecUncorrectableBlocks(t *testing.T, stream *samplestream.SampleStream[uint64], baselineValue uint64) { + dut := ondatra.DUT(t, "dut") fecStream := stream.Next() if fecStream == nil { t.Fatalf("Fec Uncorrectable Blocks was not streamed in the most recent subscription interval") } - fec, ok := fecStream.Val() + currentFec, ok := fecStream.Val() if !ok { t.Fatalf("Error capturing streaming Fec value") } - if reflect.TypeOf(fec).Kind() != reflect.Uint64 { + if reflect.TypeOf(currentFec).Kind() != reflect.Uint64 { t.Fatalf("fec value is not type uint64") } - if fec != 0 { - t.Fatalf("Got FecUncorrectableBlocks got %d, want 0", fec) + if deviations.NonIntervalFecErrorCounter(dut) { + // Check if the counter is incrementing + if currentFec > baselineValue { + t.Fatalf("FecUncorrectableBlocks increased after flap: baseline=%d, current=%d, diff=%d", + baselineValue, currentFec, currentFec-baselineValue) + } + t.Logf("FecUncorrectableBlocks not increasing: baseline=%d, current=%d", baselineValue, currentFec) + } else if currentFec != 0 { + t.Fatalf("Got FecUncorrectableBlocks got %d, want 0", currentFec) } } @@ -94,7 +103,21 @@ func TestZrUncorrectableFrames(t *testing.T) { gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) streamFecOtn := samplestream.New(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndexes[dp.Name()]).Otn().FecUncorrectableBlocks().State(), sampleInterval) defer streamFecOtn.Close() - validateFecUncorrectableBlocks(t, streamFecOtn) + // Get baseline FEC value 1 second after interface is up + time.Sleep(1 * time.Second) + baselineFecStream := streamFecOtn.Next() + if baselineFecStream == nil { + t.Fatalf("Baseline Fec Uncorrectable Blocks was not streamed") + } + baselineFec, ok := baselineFecStream.Val() + if !ok { + t.Fatalf("Error capturing baseline Fec value") + } + // Wait another 10 seconds (total 10 seconds after flap) + time.Sleep(10 * time.Second) + + // Validate that FEC errors haven't increased + validateFecUncorrectableBlocks(t, streamFecOtn, baselineFec) // Toggle interface enabled d := &oc.Root{} @@ -113,7 +136,21 @@ func TestZrUncorrectableFrames(t *testing.T) { // Wait for the cooling-off period gnmi.Await(t, dut, gnmi.OC().Interface(dp.Name()).OperStatus().State(), intUpdateTime, oc.Interface_OperStatus_UP) - validateFecUncorrectableBlocks(t, streamFecOtn) + // Get baseline FEC value 1 second after interface is up + time.Sleep(1 * time.Second) + baselineFecStream1 := streamFecOtn.Next() + if baselineFecStream1 == nil { + t.Fatalf("Baseline Fec Uncorrectable Blocks was not streamed") + } + baselineFec1, ok := baselineFecStream1.Val() + if !ok { + t.Fatalf("Error capturing baseline Fec value") + } + // Wait another 10 seconds (total 10 seconds after flap) + time.Sleep(10 * time.Second) + + // Validate that FEC errors haven't increased + validateFecUncorrectableBlocks(t, streamFecOtn, baselineFec1) }) } } diff --git a/feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/README.md b/feature/platform/transceiver/tests/zr_firmware_version_test/README.md similarity index 100% rename from feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/README.md rename to feature/platform/transceiver/tests/zr_firmware_version_test/README.md diff --git a/feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/metadata.textproto b/feature/platform/transceiver/tests/zr_firmware_version_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_firmware_version_test/metadata.textproto diff --git a/feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/zr_firmware_version_test.go b/feature/platform/transceiver/tests/zr_firmware_version_test/zr_firmware_version_test.go similarity index 98% rename from feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/zr_firmware_version_test.go rename to feature/platform/transceiver/tests/zr_firmware_version_test/zr_firmware_version_test.go index 08b8e4afe97..2ae3caf6d3c 100644 --- a/feature/platform/transceiver/firmware_version/tests/zr_firmware_version_test/zr_firmware_version_test.go +++ b/feature/platform/transceiver/tests/zr_firmware_version_test/zr_firmware_version_test.go @@ -79,7 +79,7 @@ func TestZRFirmwareVersionState(t *testing.T) { t.Logf("dut1: %v", dut1) t.Logf("dut1 dp1 name: %v", dp1.Name()) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut1, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut1, operationalMode) cfgplugins.InterfaceConfig(t, dut1, dp1) cfgplugins.InterfaceConfig(t, dut1, dp2) gnmi.Await(t, dut1, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), time.Minute*2, oc.Interface_OperStatus_UP) diff --git a/feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/README.md b/feature/platform/transceiver/tests/zr_input_output_power_test/README.md similarity index 100% rename from feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/README.md rename to feature/platform/transceiver/tests/zr_input_output_power_test/README.md diff --git a/feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/metadata.textproto b/feature/platform/transceiver/tests/zr_input_output_power_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_input_output_power_test/metadata.textproto diff --git a/feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/zr_input_output_power_test.go b/feature/platform/transceiver/tests/zr_input_output_power_test/zr_input_output_power_test.go similarity index 99% rename from feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/zr_input_output_power_test.go rename to feature/platform/transceiver/tests/zr_input_output_power_test/zr_input_output_power_test.go index 2ce0c4f2deb..d11f54e0125 100644 --- a/feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/zr_input_output_power_test.go +++ b/feature/platform/transceiver/tests/zr_input_output_power_test/zr_input_output_power_test.go @@ -39,7 +39,7 @@ func TestOpticalPower(t *testing.T) { dut := ondatra.DUT(t, "dut") fptest.ConfigureDefaultNetworkInstance(t, dut) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) diff --git a/feature/platform/transceiver/inventory/tests/zr_inventory_test/README.md b/feature/platform/transceiver/tests/zr_inventory_test/README.md similarity index 100% rename from feature/platform/transceiver/inventory/tests/zr_inventory_test/README.md rename to feature/platform/transceiver/tests/zr_inventory_test/README.md diff --git a/feature/platform/transceiver/inventory/tests/zr_inventory_test/metadata.textproto b/feature/platform/transceiver/tests/zr_inventory_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/inventory/tests/zr_inventory_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_inventory_test/metadata.textproto diff --git a/feature/platform/transceiver/inventory/tests/zr_inventory_test/zr_inventory_test.go b/feature/platform/transceiver/tests/zr_inventory_test/zr_inventory_test.go similarity index 100% rename from feature/platform/transceiver/inventory/tests/zr_inventory_test/zr_inventory_test.go rename to feature/platform/transceiver/tests/zr_inventory_test/zr_inventory_test.go diff --git a/feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/README.md b/feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md similarity index 100% rename from feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/README.md rename to feature/platform/transceiver/tests/zr_laser_bias_current_test/README.md diff --git a/feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/metadata.textproto b/feature/platform/transceiver/tests/zr_laser_bias_current_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_laser_bias_current_test/metadata.textproto diff --git a/feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go b/feature/platform/transceiver/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go similarity index 93% rename from feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go rename to feature/platform/transceiver/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go index f3063fa3764..57afaa4f36c 100644 --- a/feature/platform/transceiver/laser_bias_current/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go +++ b/feature/platform/transceiver/tests/zr_laser_bias_current_test/zr_laser_bias_current_test.go @@ -15,6 +15,7 @@ package zr_laser_bias_current_test import ( + "flag" "testing" "time" @@ -29,6 +30,11 @@ import ( "github.com/openconfig/ygot/ygot" ) +var ( + operationalModeFlag = flag.Int("operational_mode", 0, "vendor-specific operational-mode for the channel.") + operationalMode uint16 +) + func TestMain(m *testing.M) { fptest.RunTests(m) } @@ -81,6 +87,8 @@ func TestZRLaserBiasCurrentState(t *testing.T) { dp2 := dut1.Port(t, "port2") t.Logf("dut1: %v", dut1) t.Logf("dut1 dp1 name: %v", dp1.Name()) + operationalMode = uint16(*operationalModeFlag) + operationalMode = cfgplugins.InterfaceInitialize(t, dut1, operationalMode) cfgplugins.InterfaceConfig(t, dut1, dp1) cfgplugins.InterfaceConfig(t, dut1, dp2) intUpdateTime := 2 * time.Minute @@ -102,6 +110,8 @@ func TestZRLaserBiasCurrentStateInterfaceFlap(t *testing.T) { dp2 := dut1.Port(t, "port2") t.Logf("dut1: %v", dut1) t.Logf("dut1 dp1 name: %v", dp1.Name()) + operationalMode = uint16(*operationalModeFlag) + operationalMode = cfgplugins.InterfaceInitialize(t, dut1, operationalMode) cfgplugins.InterfaceConfig(t, dut1, dp1) cfgplugins.InterfaceConfig(t, dut1, dp2) intUpdateTime := 2 * time.Minute @@ -139,6 +149,8 @@ func TestZRLaserBiasCurrentStateTransceiverOnOff(t *testing.T) { dp2 := dut1.Port(t, "port2") t.Logf("dut1: %v", dut1) t.Logf("dut1 dp1 name: %v", dp1.Name()) + operationalMode = uint16(*operationalModeFlag) + operationalMode = cfgplugins.InterfaceInitialize(t, dut1, operationalMode) cfgplugins.InterfaceConfig(t, dut1, dp1) cfgplugins.InterfaceConfig(t, dut1, dp2) intUpdateTime := 2 * time.Minute diff --git a/feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/README.md b/feature/platform/transceiver/tests/zr_logical_channels_test/README.md similarity index 92% rename from feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/README.md rename to feature/platform/transceiver/tests/zr_logical_channels_test/README.md index eb5081775f2..b9e445a99a9 100644 --- a/feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/README.md +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/README.md @@ -42,6 +42,41 @@ Once the ZR link is estabished proceed to configure the following entities: * /components/component/optical-channel/config/frequency * /components/component/optical-channel/config/target-output-power * /components/component/optical-channel/config/operational-mode + * /components/component/optical-channel/config/line-port +#### Canonical OC +```json +{ + "components": { + "component": [ + { + "config": { + "name": "LINE-1-1" + }, + "name": "LINE-1-1" + }, + { + "config": { + "name": "transceiver-0/0/1" + }, + "name": "transceiver-0/0/1", + "optical-channel": { + "config": { + "frequency": "193100000", + "line-port": "LINE-1-1", + "operational-mode": 1, + "target-output-power": "-10" + } + }, + "transceiver": { + "config": { + "enabled": true + } + } + } + ] + } +} +``` ### TRANSCEIVER 11.2 - Test Ethernet Logical Channels * Ensure terminal-devic ethernet-logical-channels are set through the diff --git a/feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/metadata.textproto b/feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto similarity index 94% rename from feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto index 8493789277c..729bbd64a97 100644 --- a/feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/metadata.textproto +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/metadata.textproto @@ -13,6 +13,7 @@ platform_exceptions: { default_network_instance: "default" missing_port_to_optical_channel_component_mapping: true channel_assignment_rate_class_parameters_unsupported: true + line_port_unsupported: true } } platform_exceptions: { @@ -34,5 +35,6 @@ platform_exceptions: { interface_enabled: true explicit_dco_config: true eth_channel_ingress_parameters_unsupported: true + line_port_unsupported: true } } diff --git a/feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/zr_logical_channels_test.go b/feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go similarity index 97% rename from feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/zr_logical_channels_test.go rename to feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go index 6269b676369..e5bf6f25e14 100644 --- a/feature/platform/transceiver/logical_channels/tests/zr_logical_channels_test/zr_logical_channels_test.go +++ b/feature/platform/transceiver/tests/zr_logical_channels_test/zr_logical_channels_test.go @@ -50,7 +50,7 @@ func Test400ZRLogicalChannels(t *testing.T) { fptest.ConfigureDefaultNetworkInstance(t, dut) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, p1) cfgplugins.InterfaceConfig(t, dut, p2) @@ -60,10 +60,10 @@ func Test400ZRLogicalChannels(t *testing.T) { tr1 := gnmi.Get(t, dut, gnmi.OC().Interface(p1.Name()).Transceiver().State()) tr2 := gnmi.Get(t, dut, gnmi.OC().Interface(p2.Name()).Transceiver().State()) - cfgplugins.ConfigOpticalChannel(t, dut, oc1, frequency, targetOutputPower, operationalMode) + cfgplugins.ConfigOpticalChannel(t, dut, oc1, frequency, targetOutputPower, operationalMode, cfgplugins.WithLinePort(dut, oc1)) cfgplugins.ConfigOTNChannel(t, dut, oc1, otnIndex1, ethernetIndex1) cfgplugins.ConfigETHChannel(t, dut, p1.Name(), tr1, otnIndex1, ethernetIndex1) - cfgplugins.ConfigOpticalChannel(t, dut, oc2, frequency, targetOutputPower, operationalMode) + cfgplugins.ConfigOpticalChannel(t, dut, oc2, frequency, targetOutputPower, operationalMode, cfgplugins.WithLinePort(dut, oc2)) cfgplugins.ConfigOTNChannel(t, dut, oc2, otnIndex2, ethernetIndex2) cfgplugins.ConfigETHChannel(t, dut, p2.Name(), tr2, otnIndex2, ethernetIndex2) diff --git a/feature/platform/transceiver/low_power_mode/tests/zr_low_power_mode_test/README.md b/feature/platform/transceiver/tests/zr_low_power_mode_test/README.md similarity index 100% rename from feature/platform/transceiver/low_power_mode/tests/zr_low_power_mode_test/README.md rename to feature/platform/transceiver/tests/zr_low_power_mode_test/README.md diff --git a/feature/platform/transceiver/low_power_mode/tests/zr_low_power_mode_test/metadata.textproto b/feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/low_power_mode/tests/zr_low_power_mode_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_low_power_mode_test/metadata.textproto diff --git a/feature/platform/transceiver/low_power_mode/tests/zr_low_power_mode_test/zr_low_power_mode_test.go b/feature/platform/transceiver/tests/zr_low_power_mode_test/zr_low_power_mode_test.go similarity index 100% rename from feature/platform/transceiver/low_power_mode/tests/zr_low_power_mode_test/zr_low_power_mode_test.go rename to feature/platform/transceiver/tests/zr_low_power_mode_test/zr_low_power_mode_test.go diff --git a/feature/platform/transceiver/performance_metrics/tests/zr_pm_test/README.md b/feature/platform/transceiver/tests/zr_pm_test/README.md similarity index 100% rename from feature/platform/transceiver/performance_metrics/tests/zr_pm_test/README.md rename to feature/platform/transceiver/tests/zr_pm_test/README.md diff --git a/feature/platform/transceiver/performance_metrics/tests/zr_pm_test/metadata.textproto b/feature/platform/transceiver/tests/zr_pm_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/performance_metrics/tests/zr_pm_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_pm_test/metadata.textproto diff --git a/feature/platform/transceiver/performance_metrics/tests/zr_pm_test/zr_pm_test.go b/feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go similarity index 99% rename from feature/platform/transceiver/performance_metrics/tests/zr_pm_test/zr_pm_test.go rename to feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go index f54d0e68065..e7eea217b39 100644 --- a/feature/platform/transceiver/performance_metrics/tests/zr_pm_test/zr_pm_test.go +++ b/feature/platform/transceiver/tests/zr_pm_test/zr_pm_test.go @@ -48,7 +48,7 @@ func TestPM(t *testing.T) { fptest.ConfigureDefaultNetworkInstance(t, dut) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) diff --git a/feature/platform/transceiver/supply_voltage/tests/zr_supply_voltage_test/README.md b/feature/platform/transceiver/tests/zr_supply_voltage_test/README.md similarity index 100% rename from feature/platform/transceiver/supply_voltage/tests/zr_supply_voltage_test/README.md rename to feature/platform/transceiver/tests/zr_supply_voltage_test/README.md diff --git a/feature/platform/transceiver/supply_voltage/tests/zr_supply_voltage_test/metadata.textproto b/feature/platform/transceiver/tests/zr_supply_voltage_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/supply_voltage/tests/zr_supply_voltage_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_supply_voltage_test/metadata.textproto diff --git a/feature/platform/transceiver/supply_voltage/tests/zr_supply_voltage_test/zr_supply_voltage_test.go b/feature/platform/transceiver/tests/zr_supply_voltage_test/zr_supply_voltage_test.go similarity index 100% rename from feature/platform/transceiver/supply_voltage/tests/zr_supply_voltage_test/zr_supply_voltage_test.go rename to feature/platform/transceiver/tests/zr_supply_voltage_test/zr_supply_voltage_test.go diff --git a/feature/platform/transceiver/temperature/tests/zr_temperature_test/README.md b/feature/platform/transceiver/tests/zr_temperature_test/README.md similarity index 100% rename from feature/platform/transceiver/temperature/tests/zr_temperature_test/README.md rename to feature/platform/transceiver/tests/zr_temperature_test/README.md diff --git a/feature/platform/transceiver/temperature/tests/zr_temperature_test/metadata.textproto b/feature/platform/transceiver/tests/zr_temperature_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/temperature/tests/zr_temperature_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_temperature_test/metadata.textproto diff --git a/feature/platform/transceiver/temperature/tests/zr_temperature_test/zr_temperature_test.go b/feature/platform/transceiver/tests/zr_temperature_test/zr_temperature_test.go similarity index 100% rename from feature/platform/transceiver/temperature/tests/zr_temperature_test/zr_temperature_test.go rename to feature/platform/transceiver/tests/zr_temperature_test/zr_temperature_test.go diff --git a/feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/README.md b/feature/platform/transceiver/tests/zr_tunable_parameters_test/README.md similarity index 100% rename from feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/README.md rename to feature/platform/transceiver/tests/zr_tunable_parameters_test/README.md diff --git a/feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/metadata.textproto b/feature/platform/transceiver/tests/zr_tunable_parameters_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/metadata.textproto rename to feature/platform/transceiver/tests/zr_tunable_parameters_test/metadata.textproto diff --git a/feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go b/feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go similarity index 98% rename from feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go rename to feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go index 9b1f1781fc8..7b1e89c3e4c 100644 --- a/feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go +++ b/feature/platform/transceiver/tests/zr_tunable_parameters_test/zr_tunable_parameters_test.go @@ -37,7 +37,7 @@ func Test400ZRTunableFrequency(t *testing.T) { fptest.ConfigureDefaultNetworkInstance(t, dut) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) oc1 := opticalChannelFromPort(t, dut, p1) @@ -123,7 +123,7 @@ func Test400ZRTunableOutputPower(t *testing.T) { fptest.ConfigureDefaultNetworkInstance(t, dut) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) oc1 := opticalChannelFromPort(t, dut, p1) @@ -201,7 +201,7 @@ func Test400ZRInterfaceFlap(t *testing.T) { fptest.ConfigureDefaultNetworkInstance(t, dut) operationalMode = uint16(*operationalModeFlag) - cfgplugins.InterfaceInitialize(t, dut, operationalMode) + operationalMode = cfgplugins.InterfaceInitialize(t, dut, operationalMode) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port1")) cfgplugins.InterfaceConfig(t, dut, dut.Port(t, "port2")) oc1 := opticalChannelFromPort(t, dut, p1) diff --git a/feature/platform/transceiver/chromatic_dispersion/tests/zrp_cd_test/README.md b/feature/platform/transceiver/tests/zrp_cd_test/README.md similarity index 100% rename from feature/platform/transceiver/chromatic_dispersion/tests/zrp_cd_test/README.md rename to feature/platform/transceiver/tests/zrp_cd_test/README.md diff --git a/feature/platform/transceiver/chromatic_dispersion/tests/zrp_cd_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_cd_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/chromatic_dispersion/tests/zrp_cd_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_cd_test/metadata.textproto diff --git a/feature/platform/transceiver/chromatic_dispersion/tests/zrp_cd_test/zrp_cd_test.go b/feature/platform/transceiver/tests/zrp_cd_test/zrp_cd_test.go similarity index 100% rename from feature/platform/transceiver/chromatic_dispersion/tests/zrp_cd_test/zrp_cd_test.go rename to feature/platform/transceiver/tests/zrp_cd_test/zrp_cd_test.go diff --git a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zrp_fec_uncorrectable_frames_test/README.md b/feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/README.md similarity index 100% rename from feature/platform/transceiver/fec_uncorrectable_frames/tests/zrp_fec_uncorrectable_frames_test/README.md rename to feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/README.md diff --git a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zrp_fec_uncorrectable_frames_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/fec_uncorrectable_frames/tests/zrp_fec_uncorrectable_frames_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/metadata.textproto diff --git a/feature/platform/transceiver/fec_uncorrectable_frames/tests/zrp_fec_uncorrectable_frames_test/zrp_fec_uncorrectable_frames_test.go b/feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/zrp_fec_uncorrectable_frames_test.go similarity index 100% rename from feature/platform/transceiver/fec_uncorrectable_frames/tests/zrp_fec_uncorrectable_frames_test/zrp_fec_uncorrectable_frames_test.go rename to feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/zrp_fec_uncorrectable_frames_test.go diff --git a/feature/platform/transceiver/firmware_version/tests/zrp_firmware_version_test/README.md b/feature/platform/transceiver/tests/zrp_firmware_version_test/README.md similarity index 100% rename from feature/platform/transceiver/firmware_version/tests/zrp_firmware_version_test/README.md rename to feature/platform/transceiver/tests/zrp_firmware_version_test/README.md diff --git a/feature/platform/transceiver/firmware_version/tests/zrp_firmware_version_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_firmware_version_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/firmware_version/tests/zrp_firmware_version_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_firmware_version_test/metadata.textproto diff --git a/feature/platform/transceiver/firmware_version/tests/zrp_firmware_version_test/zrp_firmware_version_test.go b/feature/platform/transceiver/tests/zrp_firmware_version_test/zrp_firmware_version_test.go similarity index 100% rename from feature/platform/transceiver/firmware_version/tests/zrp_firmware_version_test/zrp_firmware_version_test.go rename to feature/platform/transceiver/tests/zrp_firmware_version_test/zrp_firmware_version_test.go diff --git a/feature/platform/transceiver/input_output_power/tests/zrp_input_output_power_test/README.md b/feature/platform/transceiver/tests/zrp_input_output_power_test/README.md similarity index 100% rename from feature/platform/transceiver/input_output_power/tests/zrp_input_output_power_test/README.md rename to feature/platform/transceiver/tests/zrp_input_output_power_test/README.md diff --git a/feature/platform/transceiver/input_output_power/tests/zrp_input_output_power_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_input_output_power_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/input_output_power/tests/zrp_input_output_power_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_input_output_power_test/metadata.textproto diff --git a/feature/platform/transceiver/input_output_power/tests/zrp_input_output_power_test/zrp_input_output_power_test.go b/feature/platform/transceiver/tests/zrp_input_output_power_test/zrp_input_output_power_test.go similarity index 100% rename from feature/platform/transceiver/input_output_power/tests/zrp_input_output_power_test/zrp_input_output_power_test.go rename to feature/platform/transceiver/tests/zrp_input_output_power_test/zrp_input_output_power_test.go diff --git a/feature/platform/transceiver/inventory/tests/zrp_inventory_test/README.md b/feature/platform/transceiver/tests/zrp_inventory_test/README.md similarity index 100% rename from feature/platform/transceiver/inventory/tests/zrp_inventory_test/README.md rename to feature/platform/transceiver/tests/zrp_inventory_test/README.md diff --git a/feature/platform/transceiver/inventory/tests/zrp_inventory_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_inventory_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/inventory/tests/zrp_inventory_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_inventory_test/metadata.textproto diff --git a/feature/platform/transceiver/inventory/tests/zrp_inventory_test/zrp_inventory_test.go b/feature/platform/transceiver/tests/zrp_inventory_test/zrp_inventory_test.go similarity index 94% rename from feature/platform/transceiver/inventory/tests/zrp_inventory_test/zrp_inventory_test.go rename to feature/platform/transceiver/tests/zrp_inventory_test/zrp_inventory_test.go index 22d3e04059e..f4b7d2d076e 100644 --- a/feature/platform/transceiver/inventory/tests/zrp_inventory_test/zrp_inventory_test.go +++ b/feature/platform/transceiver/tests/zrp_inventory_test/zrp_inventory_test.go @@ -14,6 +14,7 @@ import ( "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" ) const ( @@ -145,7 +146,7 @@ func TestInventoryTransceiverOnOff(t *testing.T) { dp1 := dut.Port(t, "port1") dp2 := dut.Port(t, "port2") tr1 := gnmi.Get(t, dut, gnmi.OC().Interface(dp1.Name()).Transceiver().State()) - // tr2 := gnmi.Get(t, dut, gnmi.OC().Interface(dp2.Name()).Transceiver().State()) + tr2 := gnmi.Get(t, dut, gnmi.OC().Interface(dp2.Name()).Transceiver().State()) och1 := components.OpticalChannelComponentFromPort(t, dut, dp1) och2 := components.OpticalChannelComponentFromPort(t, dut, dp2) fptest.ConfigureDefaultNetworkInstance(t, dut) @@ -186,6 +187,10 @@ func TestInventoryTransceiverOnOff(t *testing.T) { // for transceiver disable, the input needs to be the transceiver name instead of the interface name tr := gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) gnmi.Update(t, dut, gnmi.OC().Component(p.Name()).Name().Config(), p.Name()) + setConfigLeaf := gnmi.OC().Component(tr) + gnmi.Update(t, dut, setConfigLeaf.Config(), &oc.Component{ + Name: ygot.String(tr), + }) gnmi.Update(t, dut, gnmi.OC().Component(tr).Transceiver().Enabled().Config(), false) } t.Logf("Interfaces are down: %v, %v", dp1.Name(), dp2.Name()) @@ -193,11 +198,10 @@ func TestInventoryTransceiverOnOff(t *testing.T) { time.Sleep(3 * waitInterval) // power on interface transceiver. - for _, p := range dut.Ports() { - tr := gnmi.Get(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()) - gnmi.Update(t, dut, gnmi.OC().Component(p.Name()).Name().Config(), p.Name()) - gnmi.Update(t, dut, gnmi.OC().Component(tr).Transceiver().Enabled().Config(), true) - } + gnmi.Update(t, dut, gnmi.OC().Component(dp1.Name()).Name().Config(), dp1.Name()) + gnmi.Update(t, dut, gnmi.OC().Component(tr1).Transceiver().Enabled().Config(), true) + gnmi.Update(t, dut, gnmi.OC().Component(dp2.Name()).Name().Config(), dp2.Name()) + gnmi.Update(t, dut, gnmi.OC().Component(tr2).Transceiver().Enabled().Config(), true) // Wait for channels to be up. gnmi.Await(t, dut, gnmi.OC().Interface(dp1.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) gnmi.Await(t, dut, gnmi.OC().Interface(dp2.Name()).OperStatus().State(), timeout, oc.Interface_OperStatus_UP) diff --git a/feature/platform/transceiver/laser_bias_current/tests/zrp_laser_bias_current_test/README.md b/feature/platform/transceiver/tests/zrp_laser_bias_current_test/README.md similarity index 100% rename from feature/platform/transceiver/laser_bias_current/tests/zrp_laser_bias_current_test/README.md rename to feature/platform/transceiver/tests/zrp_laser_bias_current_test/README.md diff --git a/feature/platform/transceiver/laser_bias_current/tests/zrp_laser_bias_current_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_laser_bias_current_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/laser_bias_current/tests/zrp_laser_bias_current_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_laser_bias_current_test/metadata.textproto diff --git a/feature/platform/transceiver/laser_bias_current/tests/zrp_laser_bias_current_test/zrp_laser_bias_current_test.go b/feature/platform/transceiver/tests/zrp_laser_bias_current_test/zrp_laser_bias_current_test.go similarity index 100% rename from feature/platform/transceiver/laser_bias_current/tests/zrp_laser_bias_current_test/zrp_laser_bias_current_test.go rename to feature/platform/transceiver/tests/zrp_laser_bias_current_test/zrp_laser_bias_current_test.go diff --git a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/README.md b/feature/platform/transceiver/tests/zrp_logical_channels_test/README.md similarity index 100% rename from feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/README.md rename to feature/platform/transceiver/tests/zrp_logical_channels_test/README.md diff --git a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_logical_channels_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_logical_channels_test/metadata.textproto diff --git a/feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/zrp_logical_channels_test.go b/feature/platform/transceiver/tests/zrp_logical_channels_test/zrp_logical_channels_test.go similarity index 100% rename from feature/platform/transceiver/logical_channels/tests/zrp_logical_channels_test/zrp_logical_channels_test.go rename to feature/platform/transceiver/tests/zrp_logical_channels_test/zrp_logical_channels_test.go diff --git a/feature/platform/transceiver/low_power_mode/tests/zrp_low_power_mode_test/README.md b/feature/platform/transceiver/tests/zrp_low_power_mode_test/README.md similarity index 100% rename from feature/platform/transceiver/low_power_mode/tests/zrp_low_power_mode_test/README.md rename to feature/platform/transceiver/tests/zrp_low_power_mode_test/README.md diff --git a/feature/platform/transceiver/low_power_mode/tests/zrp_low_power_mode_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_low_power_mode_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/low_power_mode/tests/zrp_low_power_mode_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_low_power_mode_test/metadata.textproto diff --git a/feature/platform/transceiver/low_power_mode/tests/zrp_low_power_mode_test/zrp_low_power_mode_test.go b/feature/platform/transceiver/tests/zrp_low_power_mode_test/zrp_low_power_mode_test.go similarity index 100% rename from feature/platform/transceiver/low_power_mode/tests/zrp_low_power_mode_test/zrp_low_power_mode_test.go rename to feature/platform/transceiver/tests/zrp_low_power_mode_test/zrp_low_power_mode_test.go diff --git a/feature/platform/transceiver/performance_metrics/tests/zrp_pm_test/README.md b/feature/platform/transceiver/tests/zrp_pm_test/README.md similarity index 100% rename from feature/platform/transceiver/performance_metrics/tests/zrp_pm_test/README.md rename to feature/platform/transceiver/tests/zrp_pm_test/README.md diff --git a/feature/platform/transceiver/performance_metrics/tests/zrp_pm_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_pm_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/performance_metrics/tests/zrp_pm_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_pm_test/metadata.textproto diff --git a/feature/platform/transceiver/performance_metrics/tests/zrp_pm_test/zrp_pm_test.go b/feature/platform/transceiver/tests/zrp_pm_test/zrp_pm_test.go similarity index 100% rename from feature/platform/transceiver/performance_metrics/tests/zrp_pm_test/zrp_pm_test.go rename to feature/platform/transceiver/tests/zrp_pm_test/zrp_pm_test.go diff --git a/feature/platform/transceiver/supply_voltage/tests/zrp_supply_voltage_test/README.md b/feature/platform/transceiver/tests/zrp_supply_voltage_test/README.md similarity index 100% rename from feature/platform/transceiver/supply_voltage/tests/zrp_supply_voltage_test/README.md rename to feature/platform/transceiver/tests/zrp_supply_voltage_test/README.md diff --git a/feature/platform/transceiver/supply_voltage/tests/zrp_supply_voltage_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_supply_voltage_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/supply_voltage/tests/zrp_supply_voltage_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_supply_voltage_test/metadata.textproto diff --git a/feature/platform/transceiver/supply_voltage/tests/zrp_supply_voltage_test/zrp_supply_voltage_test.go b/feature/platform/transceiver/tests/zrp_supply_voltage_test/zrp_supply_voltage_test.go similarity index 100% rename from feature/platform/transceiver/supply_voltage/tests/zrp_supply_voltage_test/zrp_supply_voltage_test.go rename to feature/platform/transceiver/tests/zrp_supply_voltage_test/zrp_supply_voltage_test.go diff --git a/feature/platform/transceiver/temperature/tests/zrp_temperature_test/README.md b/feature/platform/transceiver/tests/zrp_temperature_test/README.md similarity index 100% rename from feature/platform/transceiver/temperature/tests/zrp_temperature_test/README.md rename to feature/platform/transceiver/tests/zrp_temperature_test/README.md diff --git a/feature/platform/transceiver/temperature/tests/zrp_temperature_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_temperature_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/temperature/tests/zrp_temperature_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_temperature_test/metadata.textproto diff --git a/feature/platform/transceiver/temperature/tests/zrp_temperature_test/zrp_temperature_test.go b/feature/platform/transceiver/tests/zrp_temperature_test/zrp_temperature_test.go similarity index 100% rename from feature/platform/transceiver/temperature/tests/zrp_temperature_test/zrp_temperature_test.go rename to feature/platform/transceiver/tests/zrp_temperature_test/zrp_temperature_test.go diff --git a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/README.md b/feature/platform/transceiver/tests/zrp_tunable_parameters_test/README.md similarity index 100% rename from feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/README.md rename to feature/platform/transceiver/tests/zrp_tunable_parameters_test/README.md diff --git a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/metadata.textproto b/feature/platform/transceiver/tests/zrp_tunable_parameters_test/metadata.textproto similarity index 100% rename from feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/metadata.textproto rename to feature/platform/transceiver/tests/zrp_tunable_parameters_test/metadata.textproto diff --git a/feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go b/feature/platform/transceiver/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go similarity index 100% rename from feature/platform/transceiver/tunable_parameters/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go rename to feature/platform/transceiver/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go diff --git a/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/README.md b/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/README.md new file mode 100644 index 00000000000..0c28454904f --- /dev/null +++ b/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/README.md @@ -0,0 +1,262 @@ +# PF-1.4: GUEv1 Decapsulation rule using destination-address-prefix-set and TTL and DSCP behavior test + +## Summary + +This is to test the functionality of decapsulation of static GUEv1 to IPv4 or IPv6 payload ([GUE Variant 1](https://datatracker.ietf.org/doc/html/draft-ietf-intarea-gue-09#section-4)). These tests verify the use case of IPv4 and IPv6 encapsulated traffic in IPv4 GUE tunnel. + +The tests validate that the DUT performs the following action- + + - Decapsulate the outer (UDPoIPv4) headers of GUE packets destined to the locally configured decap IPv4 over DECAP subnet range and matching UDP port. + - Decapsulate the packet only if it matches the locally configured decap IPv4 address/addresses and matching UDP port port/port-range. + - Post decapsulation the DUT should + - Maintain the inner TTL (do not copy outer TTL to the inner TTL) + - Maintain the inner DSCP (do not copy outer DSCP to inner DSCP) + - Post decapsulation, the inner TTL value will be decremented before egressing to next-hop + - Traffic not subject to match criteria will be forwared using the traditional IP forwarding. + + Comprehensive GUEv1 decapsulation and ECMP hashing test for GUE flows with IPv4|UDP outer header on decapsulation node is documented in PF-1.22 + +## Procedure + +### Test environment setup + +* Create the following connections: +* DUT has ingress and egress port connected to the ATE. + +```mermaid +graph LR; +A[ATE:Port1] --Ingress--> B[Port1:DUT:Port2];B --Egress--> C[Port2:ATE]; +``` + +### ATE Configuration + +* ATE Port 1: Generates GUE-encapsulated traffic +* ATE Port 2: Receives traffic + +* ATE Port 1 hosted prefixes: + + * ATE-Port1 IPV4 address = ATE-P1-Address + * Additional Source Address advertised + - IPV4-SRC1 + - IPV6-SRC1 + +* ATE Port 2 hosted prefixes: + + * ATE-Port2 IPV4 address = ATE-P2-Address + * Additional destination address advertised + - IPV4-DST1 + - IPV6-DST1 + +* ATE Port 1 generates below flow types: + +| Flow type | Payload Description | Header Level | Source IP | Destination IP | Source Port | Destination Port | DSCP | TTL | +|-------------|-----------------------|----------------|------------------|--------------------------------------------------------------------------------------------|-----------------------------------------|----------------------------------------------|------|-----| +| Flow type#1 | IP+UDP+IPv4 Payload | Outer IPGUE | ATE-P1-Address | Randomized: DECAP-DST_1/32, DECAP-DST_2/32, DECAP-DST_3/32, DECAP-DST_4/32 | Varies depending on the application | 6081 (DUT configured decap port) | 35 | 70 | +| | | Inner IPv4 | IPV4-SRC1 | IPV4-DST1 | N/A | N/A | 32 | 50 | +| Flow type#2 | IP+UDP+IPv6 Payload | Outer IPGUE | ATE-P1-Address | Randomized: DECAP-DST_1/32, DECAP-DST_2/32, DECAP-DST_3/32, DECAP-DST_4/32 | Varies depending on the application | 6081 (DUT configured decap port) | 35 | 70 | +| | | Inner IPv6 | IPV6-SRC1 | IPV6-DST1 | N/A | N/A | 32 | 50 | +| Flow type#3 | IP+UDP+IPv4 Payload | Outer IPGUE | ATE-P1-Address | Randomized: DECAP-DST_1/32, DECAP-DST_2/32, DECAP-DST_3/32, DECAP-DST_4/32 | Varies depending on the application | 6085 (DUT unconfigured decap port) | 35 | 70 | +| | | Inner IPv4 | IPV4-SRC1 | IPV4-DST1 | N/A | N/A | 32 | 50 | +| Flow type#4 | IP+UDP+IPv6 Payload | Outer IPGUE | ATE-P1-Address | Randomized: DECAP-DST_1/32, DECAP-DST_2/32, DECAP-DST_3/32, DECAP-DST_4/32 | Varies depending on the application | 6085 (DUT unconfigured decap port) | 35 | 70 | +| | | Inner IPv6 | IPV6-SRC1 | IPV6-DST1 | N/A | N/A | 32 | 50 | +| Flow type#5 | IP+UDP+IPv4 Payload | Outer IPGUE | ATE-P1-Address | ATE-P2-Address | Varies depending on the application | 6081 (DUT configured decap port) | 35 | 70 | +| | | Inner IPv4 | IPV4-SRC1 | IPV4-DST1 | N/A | N/A | 32 | 50 | +| Flow type#6 | IP+UDP+IPv6 Payload | Outer IPGUE | ATE-P1-Address | ATE-P2-Address | Varies depending on the application | 6081 (DUT configured decap port) | 35 | 70 | +| | | Inner IPv6 | IPV6-SRC1 | IPV6-DST1 | N/A | N/A | 32 | 50 | + +* ATE Port 2 receives below flow/packet types: + +| RX-Flow-type | Payload Type | Header Level | Source IP | Destination IP | DSCP | TTL | UDP Source Port | UDP Destination Port | +|----------------|---------------------|----------------|------------------|------------------|------|-----|-----------------------------------------|----------------------------------------------| +| RX-Flow-type#1 | IPv4 Payload | N/A | IPV4-SRC1 | IPV4-DST1 | 32 | 49 | N/A | N/A | +| RX-Flow-type#2 | IPv6 Payload | N/A | IPV6-SRC1 | IPV6-DST1 | 32 | 49 | N/A | N/A | +| RX-Flow-type#5 | IP+UDP+IPv4 Payload | Outer IPGUE | ATE-P1-Address | ATE-P2-Address | 35 | 68 | Varies depending on the application | 6081 (configured non-default on DUT) | +| | | Inner IPv4 | IPV4-SRC1 | IPV4-DST1 | 32 | 50 | N/A | N/A | +| RX-Flow-type#6 | IP+UDP+IPv6 Payload | Outer IPGUE | ATE-P1-Address | ATE-P2-Address | 35 | 68 | Varies depending on the application | 6081 (configured non-default on DUT) | +| | | Inner IPv6 | IPV6-SRC1 | IPV6-DST1 | 32 | 50 | N/A | N/A | + + ### DUT Configuration + +1. Interfaces: Configure all DUT ports as singleton IP interfaces towards the ATE ports + +2. GUE Decapsulation: + * Configure a Loopback address with DECAP-DST_3/32 address + * Configure static routes for destination IPV4-SRC1 and IPV6-SRC1 towards ATE Port 1 + * Configure static routes for destination IPV4-DST1 and IPV6-DST1 towards ATE Port 2 + * Configure the DUT as GUEv1 decapsulator with below fields: + - DECAP-DST-SUBNET/30 as decapsulation subnet-range + - DECAP-DST/32 is one of the IP prefix from DECAP-DST-SUBNET/30 + - Do not enable copying TTL bits from outer to inner header post decapsulation + - Use single decapsulation UDP destination port 6081 for both IPv4 and IPv6 payload type + * Configure IXIA with a static route for `DECAP-DST-SUBNET/30` towards the `DUT<>ATE Port 1` + +* DECAP-DST-SUBNET/30 subnet comprises of below IP prefixes: + - DECAP-DST_1/32 + - DECAP-DST_2/32 + - DECAP-DST_3/32 + - DECAP-DST_4/32 + +* DUT hosted IPv4 prefixes: + * Loopback Address = DECAP-DST_3/32 + +### Test Procedure +Traffic: +- Generate the applicable GUE-encapsulated flow from ATE Port 1 + - Send 1000 packets at the 10% of the line rate. + - Keep the frame size [Internet Mix](https://datatracker.ietf.org/doc/html/rfc6985#section-4). + - Do not include the 16,000 MTU frame size in the testing, as DUT is not enabled to support it. + + +### PF-1.4.1: GUE Decapsulation of inner IPv4 traffic over DECAP subnet range +- Push DUT configuration +- Initiate traffic flow type#1 +- Verification: + - The outer header destination IP of the traffic falls within the configured decap range (DECAP-DST-SUBNET/30) + - The outer header destination port of the traffic (UDP 6081) matches the configured decap port criteria + - The DUT will decapsulate the outer header and perform the lookup based on the inner IP address + - The TTL bits will not be copied to the inner header + - The inner header TTL value will be decremented by 1 + - The inner packet's DSCP value (32) will be preserved + - The DUT will forward the traffic towards ATE Port 2 + - The relevant DUT counters will reflect 1000 decapsulated packets + - ATE Port 2 receives 1000 packets structured as RX-Flow-type#2 + - No packet loss should be observed + +### PF-1.4.2: GUE Decapsulation of inner IPv6 traffic over DECAP subnet range +- Push DUT configuration. +- Initiate traffic flow type#2 +- Verification: + - The outer header destination IP of the traffic falls within the configured decap range (DECAP-DST-SUBNET/30) + - The outer header destination port of the traffic (UDP 6081) matches the configured decap port criteria + - The DUT will decapsulate the outer header and perform the lookup based on the inner IP address + - The TTL bits will not be copied to the inner header + - The inner header TTL value will be decremented by 1 + - The inner packet's DSCP value (32) will be preserved + - The DUT will forward the traffic towards ATE Port 2 + - The relevant DUT counters will reflect 1000 decapsulated packets + - ATE Port 2 receives 1000 packets structured as RX-Flow-type#4 + - No packet loss should be observed + +### PF-1.4.3: GUE Decapsulation of inner IPv4 traffic using non-default and unconfigured GUE UDP port (Negative). +- Push DUT configuration. +- Initiate traffic flow type#3 +- Verification: + - The outer header destination IP of the traffic falls within the DECAP-DST-SUBNET/30 range + - The outer header destination UDP port (6085) of the traffic is not configured for decapsulation, therefore it does not match the decapsulation criteria. + - The DUT should not decapsulate these packets. Packets with `DECAP-DST_3/32` will be sent to the device controller/CPU while the packets with DECAP-DST_1/32, DECAP-DST_2/32 and DECAP-DST_4/32 will be dropped due to specific no-route to the destination present in local FIB + - The DUT decapsulation counters should not increment for this flow + - 100% packet loss should be observed on ATE Port 2 + +### PF-1.4.4: GUE Decapsulation of inner IPv6 traffic using non-default and unconfigured GUE UDP port (Negative). +- Push DUT configuration. +- Initiate traffic flow type#4 +- Verification: + - The outer header destination IP of the traffic falls within the DECAP-DST-SUBNET/30 range + - The outer header destination UDP port (6085) of the traffic is not configured for decapsulation, therefore it does not match the decapsulation criteria. + - The DUT should not decapsulate these packets. Packets should be dropped since no specific drop rule exists for unmatched GUE + - The DUT decapsulation counters should not increment for this flow + - The DUT drop counters will reflect the packets to 1000 + - 100% packet loss should be observed on ATE Port 2 + +### PF-1.4.5: Inner IPV4 GUE Pass-through (Negative) +- Push DUT configuration. +- Initiate traffic flow type#5 +- Verification: + - The outer header destination UDP port (6081) matches a configured decap port + - The outer header destination IP (ATE-P2-Address) of the traffic does not fall within the configured decap range (DECAP-DST-SUBNET/30), therefore it does not match the decapsulation criteria for the destination IP + - The DUT will not decapsulate the outer header. Instead, it will perform a lookup based on the outer destination IP address and forward the packets as standard IP traffic + - ATE Port 2 receives 1000 packets structured as RX-Flow-type#7 (original GUE encapsulated packets, outer TTL decremented by DUT) + - No packet loss should be observed for this flow + +### PF-1.4.6: Inner IPV6 GUE Pass-through (Negative) +- Push DUT configuration. +- Initiate traffic flow type#6 + - Verification: + - The outer header destination UDP port (6081) matches a configured decap port + - The outer header destination IP (ATE-P2-Address) of the traffic does not fall within the configured decap range (DECAP-DST-SUBNET/30), therefore it does not match the decapsulation criteria for the destination IP + - The DUT will not decapsulate the outer header. Instead, it will perform a lookup based on the outer destination IP address and forward the packets as standard IP traffic + - ATE Port 2 receives 1000 packets structured as RX-Flow-type#8 (original GUE encapsulated packets, outer TTL decremented by DUT) + - No packet loss should be observed + + ## Canonical OpenConfig for GUEv1 Decapsulation configuration +TODO: decap policy to be updated by https://github.com/openconfig/public/pull/1288 + +```json +{ + "network-instances": { + "network-instance": { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "policy-forwarding": { + "policies": { + "policy": [ + { + "config": { + "policy-id": "decap-policy" + }, + "rules": { + "rule": [ + { + "sequence-id": 1, + "config": { + "sequence-id": 1 + }, + "ipv4": { + "config": { + "destination-address-prefix-set": "dst_prefix", + "protocol": "IP_UDP" + } + }, + "transport": { + "config": { + "destination-port": 6080 + } + } + "action": { + "decapsulate-gue": true + }, + }, + ] + } + } + ] + } + } + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml + +# paths: + +/network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/ipv4/config/destination-address-prefix-set: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/ipv4/config/protocol: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/config/destination-port: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decapsulate-gue: + +# telemetry +openconfig-interfaces/interfaces/interface/state/counters/out-pkts: +openconfig-interfaces/interfaces/interface/state/counters/out-unicast-pkts: +openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-pkts: +openconfig-network-instance/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-octets: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * FFF - fixed form factor diff --git a/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/ip_guev1_static_decap_subnet_range_test.go b/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/ip_guev1_static_decap_subnet_range_test.go new file mode 100644 index 00000000000..07089658f7a --- /dev/null +++ b/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/ip_guev1_static_decap_subnet_range_test.go @@ -0,0 +1,649 @@ +// 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 ip_guev1_static_decap_subnet_range_test + +import ( + "fmt" + "net" + "os" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + packetSize = 512 + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + packetPerSecond = 1000 + timeout = 30 + trafficSleepTime = 10 + captureWait = 10 + ate1Asn = 65002 + ate2Asn = 65003 + dutAsn = 65001 + ipv4Src = "198.51.100.1" + ipv4Dst = "203.0.113.1" + ipv6Src = "2001:DB8:1::1" + ipv6Dst = "2001:DB8:2::1" + peerv4Grp1Name = "BGP-PEER-GROUP1-V4" + peerv6Grp1Name = "BGP-PEER-GROUP1-V6" + peerv4Grp2Name = "BGP-PEER-GROUP2-V4" + peerv6Grp2Name = "BGP-PEER-GROUP2-V6" + v4NetName1 = "BGPv4RR1" + v6NetName1 = "BGPv6RR1" + v4NetName2 = "BGPv4RR2" + v6NetName2 = "BGPv6RR2" + tunIp = "4.4.4.4" + policyName = "decap-policy-gue" + policyId = 1 +) + +var ( + atePort1 = attrs.Attributes{ + Name: "ateP1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort2 = attrs.Attributes{ + Name: "ateP2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutPort1 = &attrs.Attributes{ + Desc: "dutPort1", + MAC: "00:00:a1:a1:a1:a1", + IPv6: "2001:db8::1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = &attrs.Attributes{ + Desc: "dutPort2", + MAC: "00:00:b1:b1:b1:b1", + IPv6: "2001:db8::5", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +type testCase struct { + name string + ipType string + ateGuePort int + dutGuePort int + trafficDestIp string + trafficShouldPass bool + verifyCounters bool + txRxCountersMatch bool +} + +func TestIpGue1StaticDecapsulation(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + t.Log(dp1, dp2) + + // Configure DUT interfaces. + ConfigureDUTIntf(t, dut) + configureBgp(t, dut) + + // configure ATE + topo := configureATE(t) + ate.OTG().PushConfig(t, topo) + enableCapture(t, topo, "port2") + + testCases := []testCase{ + { + name: "PF-1.4.1: GUE Decapsulation of inner IPv4 traffic over DECAP subnet range", + ipType: "ipv4", + ateGuePort: 6081, + dutGuePort: 6081, + trafficDestIp: tunIp, + trafficShouldPass: true, + verifyCounters: true, + txRxCountersMatch: true, + }, + { + name: "PF-1.4.2: GUE Decapsulation of inner IPv6 traffic over DECAP subnet range", + ipType: "ipv6", + ateGuePort: 6081, + dutGuePort: 6081, + trafficDestIp: tunIp, + trafficShouldPass: true, + verifyCounters: true, + txRxCountersMatch: true, + }, + { + name: "PF-1.4.3: GUE Decapsulation of inner IPv4 traffic using non-default and unconfigured GUE UDP port (Negative).", + ipType: "ipv4", + ateGuePort: 6085, + dutGuePort: 6081, + trafficDestIp: tunIp, + trafficShouldPass: false, + verifyCounters: true, + txRxCountersMatch: false, + }, + { + name: "PF-1.4.4: GUE Decapsulation of inner IPv6 traffic using non-default and unconfigured GUE UDP port (Negative).", + ipType: "ipv6", + ateGuePort: 6085, + dutGuePort: 6081, + trafficDestIp: tunIp, + trafficShouldPass: false, + verifyCounters: true, + txRxCountersMatch: false, + }, + { + name: "PF-1.4.5: Inner IPV4 GUE Pass-through (Negative)", + ipType: "ipv4", + ateGuePort: 6081, + dutGuePort: 6081, + trafficDestIp: atePort2.IPv4, + trafficShouldPass: true, + verifyCounters: false, + txRxCountersMatch: false, + }, + { + name: "PF-1.4.6: Inner IPV6 GUE Pass-through (Negative)", + ipType: "ipv6", + ateGuePort: 6081, + dutGuePort: 6081, + trafficDestIp: atePort2.IPv4, + trafficShouldPass: true, + verifyCounters: false, + txRxCountersMatch: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + if tc.ipType == "ipv4" { + gueDecapInnerIpv4Traffic(t, dut, ate, topo, tc.ateGuePort, tc.dutGuePort, tc.trafficDestIp, tc.trafficShouldPass, tc.verifyCounters, tc.txRxCountersMatch) + } else { + gueDecapInnerIpv6Traffic(t, dut, ate, topo, tc.ateGuePort, tc.dutGuePort, tc.trafficDestIp, tc.trafficShouldPass, tc.verifyCounters, tc.txRxCountersMatch) + } + }) + } +} + +func ConfigureDUTIntf(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + p1 := dut.Port(t, "port1") + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(p1, dutPort1, dut)) + p2 := dut.Port(t, "port2") + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(p2, dutPort2, dut)) + + // Configure Network instance type on DUT + t.Log("Configure/update Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) +} + +// Configures the given DUT interface. +func configInterfaceDUT(p *ondatra.Port, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + i := a.NewOCInterface(p.Name(), dut) + s4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + + return i +} + +type bgpNeighbor struct { + as uint32 + neighborip string + isV4 bool + PeerGroupName string +} + +func configureBgp(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := &oc.Root{} + + nbr1v4 := &bgpNeighbor{as: ate1Asn, neighborip: atePort1.IPv4, isV4: true, PeerGroupName: peerv4Grp1Name} + nbr1v6 := &bgpNeighbor{as: ate1Asn, neighborip: atePort1.IPv6, isV4: false, PeerGroupName: peerv6Grp1Name} + nbr2v4 := &bgpNeighbor{as: ate2Asn, neighborip: atePort2.IPv4, isV4: true, PeerGroupName: peerv4Grp2Name} + nbr2v6 := &bgpNeighbor{as: ate2Asn, neighborip: atePort2.IPv6, isV4: false, PeerGroupName: peerv6Grp2Name} + + nbrList := []*bgpNeighbor{nbr1v4, nbr2v4, nbr1v6, nbr2v6} + + dutConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + g := bgp.GetOrCreateGlobal() + g.As = ygot.Uint32(dutAsn) + g.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Enabled = ygot.Bool(true) + g.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).Enabled = ygot.Bool(true) + g.RouterId = ygot.String(dutPort2.IPv4) + + pg1v4 := bgp.GetOrCreatePeerGroup(peerv4Grp1Name) + pg1v4.PeerAs = ygot.Uint32(ate1Asn) + + pg1v6 := bgp.GetOrCreatePeerGroup(peerv6Grp1Name) + pg1v6.PeerAs = ygot.Uint32(ate1Asn) + + pg2v4 := bgp.GetOrCreatePeerGroup(peerv4Grp2Name) + pg2v4.PeerAs = ygot.Uint32(ate1Asn) + + pg2v6 := bgp.GetOrCreatePeerGroup(peerv6Grp2Name) + pg2v6.PeerAs = ygot.Uint32(ate1Asn) + + for _, nbr := range nbrList { + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.PeerGroup = ygot.String(nbr.PeerGroupName) + nv4.PeerAs = ygot.Uint32(nbr.as) + nv4.Enabled = ygot.Bool(true) + if nbr.isV4 { + af4 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + af4.Enabled = ygot.Bool(true) + } else { + af6 := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + af6.Enabled = ygot.Bool(true) + } + } + gnmi.Replace(t, dut, dutConfPath.Config(), niProto) + +} + +// configureATE sets up the ATE interfaces and BGP configurations. +func configureATE(t *testing.T) gosnappi.Config { + topo := gosnappi.NewConfig() + t.Log("Configure ATE interface") + port1 := topo.Ports().Add().SetName("port1") + port2 := topo.Ports().Add().SetName("port2") + + port1Dev := topo.Devices().Add().SetName(atePort1.Name + ".dev") + port1Eth := port1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + port1Eth.Connection().SetPortName(port1.Name()) + port1Ipv4 := port1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + port1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + port1Ipv6 := port1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + port1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + bgp1 := port1Dev.Bgp().SetRouterId(atePort1.IPv4) + bgp4Peer1 := bgp1.Ipv4Interfaces().Add().SetIpv4Name(port1Ipv4.Name()).Peers().Add().SetName(port1Dev.Name() + ".BGP4.peer") + bgp4Peer1.SetPeerAddress(port1Ipv4.Gateway()) + bgp4Peer1.SetAsNumber(ate1Asn) + bgp4Peer1.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + net1v4 := bgp4Peer1.V4Routes().Add().SetName(v4NetName1) + net1v4.Addresses().Add().SetAddress(ipv4Src).SetPrefix(32).SetCount(1).SetStep(1) + + bgp6Peer1 := bgp1.Ipv6Interfaces().Add().SetIpv6Name(port1Ipv6.Name()).Peers().Add().SetName(port1Dev.Name() + ".BGP6.peer") + bgp6Peer1.SetPeerAddress(port1Ipv6.Gateway()) + bgp6Peer1.SetAsNumber(ate1Asn) + bgp6Peer1.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + net1v6 := bgp6Peer1.V6Routes().Add().SetName(v6NetName1) + net1v6.Addresses().Add().SetAddress(ipv6Src).SetPrefix(128).SetCount(1).SetStep(1) + + port2Dev := topo.Devices().Add().SetName(atePort2.Name + ".dev") + port2Eth := port2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + port2Eth.Connection().SetPortName(port2.Name()) + port2Ipv4 := port2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + port2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + port2Ipv6 := port2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + port2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + bgp2 := port2Dev.Bgp().SetRouterId(atePort2.IPv4) + bgp4Peer2 := bgp2.Ipv4Interfaces().Add().SetIpv4Name(port2Ipv4.Name()).Peers().Add().SetName(port2Dev.Name() + ".BGP4.peer") + bgp4Peer2.SetPeerAddress(port2Ipv4.Gateway()) + bgp4Peer2.SetAsNumber(ate2Asn) + bgp4Peer2.SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + net2v4 := bgp4Peer2.V4Routes().Add().SetName(v4NetName2) + net2v4.Addresses().Add().SetAddress(ipv4Dst).SetPrefix(32).SetCount(1).SetStep(1) + + bgp6Peer2 := bgp2.Ipv6Interfaces().Add().SetIpv6Name(port2Ipv6.Name()).Peers().Add().SetName(port2Dev.Name() + ".BGP6.peer") + bgp6Peer2.SetPeerAddress(port2Ipv6.Gateway()) + bgp6Peer2.SetAsNumber(ate2Asn) + bgp6Peer2.SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + net2v6 := bgp6Peer2.V6Routes().Add().SetName(v6NetName2) + net2v6.Addresses().Add().SetAddress(ipv6Dst).SetPrefix(128).SetCount(1).SetStep(1) + return topo +} + +func trafficStartStop(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) { + ate.OTG().StartTraffic(t) + time.Sleep(trafficSleepTime * time.Second) + ate.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, ate.OTG(), config) +} + +func protocolStart(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, config gosnappi.Config) { + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), config, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), config, "IPv6") + verifyBGPTelemetry(t, dut) +} + +func verifyTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, flowName string, trafficShouldPass bool) { + recvMetricV4 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flowName).State()) + + framesTxV4 := recvMetricV4.GetCounters().GetOutPkts() + framesRxV4 := recvMetricV4.GetCounters().GetInPkts() + + if trafficShouldPass { + t.Logf("traffic validation for flow %s. Expecting Traffic TX = RX.", flowName) + if framesTxV4 == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if framesRxV4 == framesTxV4 { + t.Logf("Traffic validation successful for [%s] FramesTx: %d FramesRx: %d", flowName, framesTxV4, framesRxV4) + } else { + t.Errorf("Traffic validation failed for [%s] FramesTx: %d FramesRx: %d", flowName, framesTxV4, framesRxV4) + } + } else { + t.Logf("traffic validation for flow %s. Expecting Traffic Loss", flowName) + if framesTxV4 == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if framesRxV4 == 0 { + t.Logf("PASS: Traffic Validation is successful as no packets received at the destination as Expected") + } else { + t.Error("FAIL: Traffic Validation is failed as no packets expected at the destination ") + } + } +} + +// startCapture starts the capture on the otg ports +func startCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) +} + +// stopCapture starts the capture on the otg ports +func stopCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, cs) +} + +func enableCapture(t *testing.T, config gosnappi.Config, port string) { + config.Captures().Clear() + t.Log("Enabling capture on ", port) + config.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) +} + +func processCapture(t *testing.T, ate *ondatra.ATEDevice, port string) string { + otg := ate.OTG() + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(port)) + time.Sleep(captureWait * time.Second) + pcapFile, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := pcapFile.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + defer pcapFile.Close() + return pcapFile.Name() +} + +func verify_policer_matched_packets(t *testing.T, dut *ondatra.DUTDevice) uint64 { + matchpackets := uint64(0) + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + if deviations.PolicyRuleCountersOCUnsupported(dut) { + t.Logf("Returning Matched Packet as Zero value due to Bug 425628787") + } else { + _, ok := gnmi.Watch(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy(policyName).Rule(policyId).MatchedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("Unable to find matched packets") + } + matchpackets = gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy(policyName).Rule(policyId).MatchedPkts().State()) + + } + return matchpackets +} + +func compare_counters(t *testing.T, intialpacket uint64, finalpacket uint64, countersmatch bool) { + t.Logf("Policer counters Before Traffic %v", intialpacket) + t.Logf("Policer counters After Traffic %v", finalpacket) + if countersmatch { + t.Logf("Traffic Packet Counters on DUT based on Policer. Expecting Packet Increment after Traffic") + if intialpacket == 0 { + t.Errorf("Fail : Unable to find the policer matched packets. Please refer the bug ID #425628787") + } else if finalpacket-intialpacket >= packetPerSecond { + t.Logf("Pass : policer counters got incremented after start and stop traffic") + } else { + t.Errorf("Fail : policer counters not incremented after start and stop traffic") + } + } else { + t.Logf("Traffic Packet Counters on DUT based on Policer. Expecting no packet Increment after Traffic ") + if intialpacket == 0 { + t.Errorf("Fail : Unable to find the policer matched packets. Please refer the bug ID #425628787") + } else if finalpacket-intialpacket == 0 { + t.Logf("Pass : policer counters did not incremented as expected") + } else { + t.Errorf("Fail : policer counters incremented unexpectedly") + } + } +} + +func gueDecapInnerIpv4Traffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config, ateUdpPort int, dutUdpPort int, destIp string, trafficValidation bool, verifyCounters bool, countersMatch bool) { + trafficID := fmt.Sprintf("Gue-Decap-Flow1-%v", ateUdpPort) + configureIPv4Traffic(t, ate, topo, trafficID, ateUdpPort, destIp) + configureDutWithGueDecap(t, dut, dutUdpPort, "ipv4") + protocolStart(t, dut, ate, topo) + startCapture(t, ate) + intialpacket1 := verify_policer_matched_packets(t, dut) + trafficStartStop(t, ate, topo) + stopCapture(t, ate) + if trafficValidation { + verifyTrafficFlow(t, ate, trafficID, true) + verifyCaptureDscpTtlValue(t, ate, "port2", 32, 49) + } else { + verifyTrafficFlow(t, ate, trafficID, false) + } + finalpacket1 := verify_policer_matched_packets(t, dut) + if verifyCounters { + compare_counters(t, intialpacket1, finalpacket1, countersMatch) + } +} + +func gueDecapInnerIpv6Traffic(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config, ateUdpPort int, dutUdpPort int, destIp string, trafficValidation bool, verifyCounters bool, countersMatch bool) { + trafficID := fmt.Sprintf("Gue-Decap-Flow1-%v", ateUdpPort) + configureIPv6Traffic(t, ate, topo, trafficID, ateUdpPort, destIp) + configureDutWithGueDecap(t, dut, dutUdpPort, "ipv6") + protocolStart(t, dut, ate, topo) + intialpacket1 := verify_policer_matched_packets(t, dut) + startCapture(t, ate) + trafficStartStop(t, ate, topo) + stopCapture(t, ate) + if trafficValidation { + verifyTrafficFlow(t, ate, trafficID, true) + verifyCaptureDscpTtlValue(t, ate, "port2", 32, 49) + } else { + verifyTrafficFlow(t, ate, trafficID, false) + } + finalpacket1 := verify_policer_matched_packets(t, dut) + if verifyCounters { + compare_counters(t, intialpacket1, finalpacket1, countersMatch) + } +} + +func configureDutWithGueDecap(t *testing.T, dut *ondatra.DUTDevice, guePort int, ipType string) { + t.Logf("Configure DUT with decapsulation UDP port %v", guePort) + ocPFParams := getDefaultOcPolicyForwardingParams(t, dut, guePort, ipType) + _, _, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + cfgplugins.DecapGroupConfigGue(t, dut, pf, ocPFParams) +} + +// getDefaultOcPolicyForwardingParams provides default parameters for the generator, +// matching the values in the provided JSON example. +func getDefaultOcPolicyForwardingParams(t *testing.T, dut *ondatra.DUTDevice, guePort int, ipType string) cfgplugins.OcPolicyForwardingParams { + return cfgplugins.OcPolicyForwardingParams{ + NetworkInstanceName: "DEFAULT", + InterfaceID: dut.Port(t, "port1").Name(), + AppliedPolicyName: policyName, + TunnelIP: tunIp, + GuePort: uint32(guePort), + IpType: ipType, + Dynamic: true, + } +} + +func configureIPv4Traffic(t *testing.T, ate *ondatra.ATEDevice, topo gosnappi.Config, trafficID string, + guePort int, destIp string) { + t.Logf("Configure Traffic from ATE with flowname %s", trafficID) + topo.Flows().Clear() + flow := topo.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{v4NetName1}).SetRxNames([]string{v4NetName2}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + outerIpHeader := flow.Packet().Add().Ipv4() + outerIpHeader.Src().SetValue(atePort1.IPv4) + outerIpHeader.Dst().SetValue(destIp) + outerIpHeader.Priority().Dscp().Phb().SetValue(uint32(35)) + outerIpHeader.TimeToLive().SetValue(uint32(70)) + udpHeader := flow.Packet().Add().Udp() + udpHeader.SrcPort().SetValue(30000) + udpHeader.DstPort().SetValue(uint32(guePort)) + innerIpHeader := flow.Packet().Add().Ipv4() + innerIpHeader.Src().SetValue(ipv4Src) + innerIpHeader.Dst().SetValue(ipv4Dst) + innerIpHeader.Priority().Dscp().Phb().SetValue(uint32(32)) + innerIpHeader.TimeToLive().SetValue(uint32(50)) + flow.Size().SetFixed(uint32(packetSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + ate.OTG().PushConfig(t, topo) +} + +func configureIPv6Traffic(t *testing.T, ate *ondatra.ATEDevice, topo gosnappi.Config, trafficID string, + guePort int, destIp string) { + t.Logf("Configure Traffic from ATE with flowname %s", trafficID) + topo.Flows().Clear() + flow := topo.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{v4NetName1}).SetRxNames([]string{v4NetName2}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + outerIpHeader := flow.Packet().Add().Ipv4() + outerIpHeader.Src().SetValue(atePort1.IPv4) + outerIpHeader.Dst().SetValue(destIp) + outerIpHeader.Priority().Dscp().Phb().SetValue(uint32(35)) + outerIpHeader.TimeToLive().SetValue(uint32(70)) + udpHeader := flow.Packet().Add().Udp() + udpHeader.SrcPort().SetValue(30000) + udpHeader.DstPort().SetValue(uint32(guePort)) + innerIpHeader := flow.Packet().Add().Ipv6() + innerIpHeader.Src().SetValue(ipv6Src) + innerIpHeader.Dst().SetValue(ipv6Dst) + innerIpHeader.TrafficClass().SetValue(uint32(32)) + innerIpHeader.HopLimit().SetValue(uint32(50)) + flow.Size().SetFixed(uint32(packetSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + ate.OTG().PushConfig(t, topo) +} + +func verifyCaptureDscpTtlValue(t *testing.T, ate *ondatra.ATEDevice, port string, dscp int, ttl int) { + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv4) + if ip.SrcIP.Equal(net.ParseIP(ipv4Src)) { + dscpValue := ip.TOS >> 2 + ttlVal := ip.TTL + if dscpValue == uint8(dscp) && ttlVal == uint8(ttl) { + t.Logf("PASS: DSCP value %v and TTL value %v are Preserved", dscp, ttl) + break + } else { + t.Fatalf("ERROR: DSCP and TTL value not preserved after Decap. Expected : DSCP - %v , TTL -%v Got : DSCP - %v , TTL -%v", dscp, ttl, dscpValue, ttlVal) + } + + } + + } + } +} + +func waitForBGPSession(t *testing.T, dut *ondatra.DUTDevice, wantEstablished bool) { + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + compare := func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if ok { + if wantEstablished { + t.Logf("BGP session state: %s", state.String()) + return state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + } + return state == oc.Bgp_Neighbor_SessionState_IDLE + } + return false + } + + nbrListv4 := []string{atePort1.IPv4, atePort2.IPv4} + + for _, nbr := range nbrListv4 { + nbrPath := statePath.Neighbor(nbr) + _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, compare).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + if wantEstablished { + t.Fatal("No BGP neighbor formed...") + } else { + t.Fatal("BGPv4 session didn't teardown.") + } + } + } +} + +func verifyBGPTelemetry(t *testing.T, dut *ondatra.DUTDevice) { + t.Log("Waiting for BGPv4 neighbor to establish...") + waitForBGPSession(t, dut, true) +} diff --git a/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/metadata.textproto b/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/metadata.textproto new file mode 100644 index 00000000000..1184a36ee8b --- /dev/null +++ b/feature/policy_forwarding/decapsulation/otg_tests/ip_guev1_static_decap_subnet_range/metadata.textproto @@ -0,0 +1,19 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "cbd90220-62bb-430f-bfe2-ec2ea73ef124" +plan_id: "PF-1.4" +description: "GUEv1 Decapsulation rule using destination-address-prefix-set and TTL and DSCP behavior test" +testbed: TESTBED_DUT_ATE_2LINKS + +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + policy_rule_counters_oc_unsupported: true + gue_gre_decap_unsupported: true + } +} \ No newline at end of file diff --git a/feature/policy_forwarding/decapsulation/otg_tests/ipv4_guev1_decap_and_hashing_test/README.md b/feature/policy_forwarding/decapsulation/otg_tests/ipv4_guev1_decap_and_hashing_test/README.md new file mode 100644 index 00000000000..0e5bed73b77 --- /dev/null +++ b/feature/policy_forwarding/decapsulation/otg_tests/ipv4_guev1_decap_and_hashing_test/README.md @@ -0,0 +1,336 @@ +# PF-1.22: GUEv1 Decapsulation and ECMP test for IPv4 and IPv6 payload +## Summary +This is to test the functionality of decapsulation of GUEv1 to IPv4 or IPv6 payload and ensures that only +the outer header (IPv4 | UDP) of GUEv1 encapsulated packets are used for hashing on decapsulating nodes. + +GUEv1 decapsulation matching destination-address-prefix-set and TTL and DSCP behavior test is documented in [#4178](https://github.com/openconfig/featureprofiles/pull/4178). + +## Topology +```mermaid +graph LR; +subgraph DUT [DUT] + B1[Port1] + B2[Port2] + B3[Port3] + B4[Port4] + B5[Port5] + B6[Port6] + B7[Port7] +end +subgraph ATE2 [ATE2] + N1[Port1] +end +subgraph ATE3 [ATE3] + N21[Port2] + N22[Port3] +end +subgraph ATE4 [ATE4] + N31[Port4] + N32[Port5] +end +subgraph ATE5 [ATE5] + N4[Port6] +end +A1[ATE1:Port1] <--EBGP--> B1; +B2 <--IBGP--> N1; +B3 <-- [LAG1] IBGP --> N21; +B4 <-- [LAG1] IBGP --> N22; +B5 <-- [LAG2] EBGP --> N31; +B6 <-- [LAG2] EBGP --> N32; +B7 <-- EBGP --> N4; +``` + +## Configuration generation of DUT and ATE + +### Baseline DUT configuration +1. The DUT's loopback interface must be passive for IS-IS. +2. Configure IPv4 and IPv6 EBGP[ASN200:ASN100] between ATE:Port1 <> DUT:Port1 +3. Configure IPv4 and IPv6 IBGP[ASN100] between DUT <> ATE2 +4. Configure IPv4 and IPv6 IBGP[ASN100] between DUT <> ATE3 +5. Configure IPv4 and IPv6 EBGP[ASN100:ASN200] between DUT <> ATE4 +6. Configure IPv4 and IPv6 EBGP[ASN100:ASN200] between DUT <> ATE5 +7. Configure DUT as decapsulation node for IP|UDP (GUE v1)using "DUT-DECAP-Address" and decap UDP port as 6080 +8. Enable BGP multipath for both EBGP and IBGP sessions to enable load balancing traffic across multiple paths/links +9. Enable BGP multihop for BGP(IBGP/EBGP) sessions on LAG interfaces +10. DUT has multiple paths to Host2 via multiple nodes, ATE2 & ATE3 +11. DUT can reach Host3 via a lag interface towards ATE3 +12. DUT has multiple paths to Host4 via multiple nodes, ATE4 & ATE5 +13. Host1(v4/v6) route is installed and active via ATE1 +14. Host2(v4/v6) route is installed and active via ATE2 and ATE3, therefore the traffic for Host2 should be load-balanced across both the nodes +15. Host3v4 route is installed and active via ATE3, therefore the traffic for Host3 should be load-balanced across the lag members +16. Host4(v4/v6) route is installed and active via ATE4 and ATE5, therefore the traffic for Host4 should be load-balanced across both the nodes + +### Baseline ATE configuration +1. The ATE's loopback interface must be passive for IS-IS +2. Establish BGP sessions as shown in the DUT configuration section +3. ATE1 hosts Host1v4 and Host1v6 +4. ATE2 and ATE3 hosts Host2v4 and Host2v6 +5. ATE3 hosts Host3v4 + - Reachability to Host3v4 from ATE3 is via a static MPLS label +6. ATE4 and ATE5 hosts Host4v4 and Host4v6 + +#### ATE Route Advertisements +| **Source ATE Nodes** | **Advertisement Type** | **Prefixes** | **Via BGP Sessions** | **Host Mapping** | +| -------------- | ---------------------- | ------------------ | ---------------------------------------| ---------------- | +| ATE1 | EBGP | IPv4prefix1-10/24 | IPv4 DUT <--> ATE1 | Host1v4 | +| ATE1 | EBGP | IPv6prefix1-10/64 | IPv6 DUT <--> ATE1 | Host1v6 | +| ATE1 | EBGP | Loopback[1-10]v4/32 | IPv4 DUT <--> ATE1 | ATE1LO[1-10]v4 | +| ATE2 and ATE3 | IBGP | IPv4prefix11-20/24 | IPv4 DUT <--> ATE2 and DUT <--> ATE3 | Host2v4 | +| ATE2 and ATE3 | IBGP | IPv6prefix11-20/64 | IPv6 DUT <--> ATE2 and DUT <--> ATE3 | Host2v6 | +| ATE3 | IBGP | IPv4prefix21-30/24 | IPv4 DUT <--> ATE2 and DUT <--> ATE3 | Host3v4 | +| ATE4 and ATE5 | EBGP | IPv4prefix31-40/24 | IPv4 DUT <--> ATE4 and DUT <--> ATE5 | Host4v4 | +| ATE4 and ATE5 | EBGP | IPv6prefix31-40/64 | IPv6 DUT <--> ATE4 and DUT <--> ATE5 | Host4v6 | + + +**_To simplify this document, Host1, Host2, and Host3 will be referred to as H1, H2, H3 and H4 respectively._** + +### Packet types +### IPv4 and IPv6 Packet Constructs Detailed Table + +| Packet# | Layer | Protocol | Source Address | Destination Address | Source Port | Destination Port | MPLS Label | Notes | +| :------- | :---------- | :---------------- | :------------------ | :------------------ | :------------------ | :------------------------ | :-----------| :------------------------------------------ | +| **1** | **Overall** | **Payload o IPv4\|TCP o MPLS o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | Inner | IPv4\|TCP | H1v4 address | H3v4 address | 14 | 15 | |Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | MPLS | MPLS | N/A | N/A | N/A | N/A | Static label for ATE3 to reach H3v4 | *Note: Inner Dst is H3v4* | +| | Middle | IPv4\|UDP | ATE1LO1v4 IPv4 addr | ATE3-port IPv4 addr | 5995 (randomizable) | 6080 | | Src Port: Any unreserved UDP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **2** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | Inner | IPv4\|UDP | H1v4 address | H2v4 address | 14 (randomizable) | 15 | | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **3** | **Overall** | **Payload o IPv4\|TCP o IPv4\|UDP(GUE v1)** | | | | | | +| | Inner | IPv4\|TCP | H1v4 address | H2v4 address | 14 (randomizable) | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **4** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | Inner | IPv4\|UDP | H1v4 address | H4v4 address | 14 (randomizable) | 15 | | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port| +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **5** | **Overall** | **Payload o IPv4\|TCP o IPv4\|UDP(GUE v1)** | | | | | | +| | Inner | IPv4\|TCP | H1v4 address | H4v4 address | 14 (randomizable) | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **6** | **Overall** | **Payload o IPv6\|TCP o MPLS o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | Inner | IPv6\|TCP | H1v6 address | H3v6 address | 14 | 15 | | Src Port: Any unassigned TCP; Dst Port: Any App/unassigned TCP | +| | MPLS | MPLS | N/A | N/A | N/A | N/A | Static label for ATE3 to reach H3v6 | *Note: Inner Dst is H3v6* | +| | Middle | IPv4\|UDP | ATE1LO1v4 IPv4 addr | ATE3-port IPv4 addr | 5995 (randomizable) | 6080 | | Src Port: Any unassigned UDP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **7** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|UDP | H1v6 address | H2v6 address | 5995 (randomizable) | 5994 (randomizable) | | Src/Dst Ports: Any unassigned UDP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **8** | **Overall** | **Payload o IPv6\|TCP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|TCP | H1v6 address | H2v6 address | 14 (randomizable) | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **9** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|UDP | H1v6 address | H4v6 address | 5995 (randomizable) | 5994 (randomizable) | | Src/Dst Ports: Any unreserved UDP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unreserved UDP port; GUE v1 encapsulation | +| **10**| **Overall** | **Payload o IPv6\|TCP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|TCP | H1v6 address | H4v6 address | 14 (randomizable) | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 (randomizable) | 6080 | | Src Port: Any unreserved UDP port; GUE v1 encapsulation | +| **11**| **Overall** | **Payload o IPv6\|TCP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|TCP | H1v4 address | H4v4 address | 14 | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | ATE2-port-Address IPv4 addr | 5996 | 6080 | | Src Port: Any unreserved UDP port; GUE v1 encapsulation | +| **12**| **Overall** | **Payload o IPv6\|TCP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|TCP | H1v6 address | H4v6 address | 14 | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | ATE2-port-Address IPv4 addr | 5996 | 6080 | | Src Port: Any unreserved UDP port; GUE v1 encapsulation | +| **13**| **Overall** | **Payload o IPv6\|TCP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|TCP | H1v4 address | H4v4 address | 14 | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6085 | | Src Port: Any unreserved UDP port; GUE v1 encapsulation | +| **14**| **Overall** | **Payload o IPv6\|TCP o IPv4\|UDP(GUE v1)** | | | | | | | +| | Inner | IPv6\|TCP | H1v6 address | H4v6 address | 14 | 15 | | Src Port: Any unassigned TCP port; Dst Port: Any App/unassigned TCP port | +| | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6085 | | Src Port: Any unreserved UDP port; GUE v1 encapsulation | + +### Flow types + +| **Flow-type** | **Description** | **Packet#** | +| -------------- | ---------------------- | ------------------ | +| 1 | H1 --> H3 with IPv4 | Packet#1 | +| 2 | H1 --> H2 with IPv4 | Packet#2 | +| 3 | H1 --> H2 with IPv4 | Packet#3 | +| 4 | H1 --> H4 with IPv4 | Packet#4 | +| 5 | H1 --> H4 with IPv4 | Packet#5 | +| 6 | H1 --> H3 with IPv6 | Packet#6 | +| 7 | H1 --> H2 with IPv6 | Packet#7 | +| 8 | H1 --> H2 with IPv6 | Packet#8 | +| 9 | H1 --> H4 with IPv6 | Packet#9 | +| 10 | H1 --> H4 with IPv6 | Packet#10 | + +## Procedure + +- Traffic towards a destination is spread evenly across nodes and LAGs (if applicable): + - Tolerance for delta: 5% +- Start the Ixia traffic as specified for test + - Sent 1000000 packets at the 10% of the line rate. + - Packets are generated based on different header field entropy which can be defined by the test case +- Repeat each test with the each ATE Flow-type or explicitly mentioned flow-type +- Conduct each of the following test, using a single flow-type with 1024 flows + +### PF-1.22.1[Baseline]: GUE Decapsulation over ipv4 decap address and Load-balance test +- Configure the DUT and ATE as stated above +- Initiate a single flow-type and follow the below stated and applicable verification steps +- L4 source port of outer header(GUEv1 encap header) should be randomized for each flow-type that's running +- Repeat the test for all flow-types +- Validations: +- The outer header destination IP of the traffic is the DUT-DECAP-Address and the destination port of the traffic (UDP 6080) matches the configured UDP decap port criteria +- Therefore, DUT will decapsulate the outer header and perform a lookup based on the inner IP address +- The following traffic distribution validations are applicable as per the flow-type that is being tested + - Flow#1 for H3 should be load-balanced across the lag members via ATE3 + - Flow#2 for H2 should be load-balanced via ATE2 and ATE3 + - Traffic via ATE3 should be load-balanced across the lag members + - Flow#3 for H2 should be load-balanced via ATE2 and ATE3 + - Traffic via ATE3 should be load-balanced across the lag members + - Flow#4 for H4 should be load-balanced via ATE4 and ATE5 + - Traffic forwarded towards ATE4 (via LAG2) should be load-balanced across the LAG members + - Flow#5 for H4 should be load-balanced via ATE4 and ATE5 + - Traffic forwarded towards ATE4 (via LAG2) should be load-balanced across the LAG members + - Flow#6 for H3 should be load-balanced across the lag members via ATE3 + - Flow#7 for H2 should be load-balanced via ATE2 and ATE3 + - Traffic via ATE3 should be load-balanced across the lag members + - Flow#8 for H2 should be load-balanced via ATE2 and ATE3 + - Traffic via ATE3 should be load-balanced across the lag members + - Flow#9 for H4 should be load-balanced via ATE4 and ATE5 + - Traffic forwarded towards ATE4 (via LAG2) should be load-balanced across the LAG members + - Flow#10 for H4 should be load-balanced via ATE4 and ATE5 + - Traffic forwarded towards ATE4 (via LAG2) should be load-balanced across the LAG members + - No packet loss should be observed + - Port traffic counters will be leveraged to verify the loab-balance behavior + +### PF-1.22.2: GUE Decapsulation over non-matching ipv4 decap address [Negative] +- Configure the DUT and ATE as stated above +- Repeat the test for flow-type#11 and flow-type#12 (one at a time) +- Validations: +- The outer header destination UDP port (6080) matches a configured decap port +- The outer header destination IP of the traffic does not matches the locally configured decapsulation address(DUT-DECAP-Address), therefore it does not match the decapsulation criteria for the destination IP +- The DUT will not decapsulate the outer header. Instead, it will perform a lookup based on the outer destination IP address and forward the packets as standard IP traffic +- ATE Port 2 receives 1000000 packets +- No packet loss should be observed + +### PF-1.22.3: GUE Decapsulation over non-matching UDP decap port [Negative] +- Configure the DUT and ATE as stated above +- Repeat the test for flow-type#13 and flow-type#14 (one at a time) +- Validations: +- The outer header destination IP of the traffic matches a configured decap IP address +- The outer header destination UDP port (6085) of the traffic does not matches the locally configured decapsulation port(6080), therefore it does not match the decapsulation criteria for the destination port +- The DUT should not decapsulate these packets. Packets should be dropped since no specific policy-forwarding rule exists for unmatched GUE +- The DUT decapsulation counters should not increment for this flow +- The drop counters on DUT will reflect the packets to 1000000 + - These drop packets count will be reflected in DUT control-plane drop counters +- 100% packet loss should be observed on ATE Port 2 + +### PF-1.22.4: Verify the Immediate next header's L4 fields are not considered in Load-Balancing Algorithm +- Set up the DUT and ATE as previously specified. +- For each flow type, configure a single source and destination IP address for both the inner and middle IPv4 and IPv6 headers. Specifically: + - For IPv4 flows that would typically use an H1v4 range, use IPv4prefix1/24. + - For IPv6 flows that would typically use an H1v6 range, use IPv6prefix1/64. + - For IPv4 flows that would typically use an H2v4 range, use IPv4prefix11/24. + - For IPv6 flows that would typically use an H2v6 range, use IPv6prefix11/64. + - For IPv4 flows that would typically use an H4v4 range, use IPv4prefix31/24. + - For IPv6 flows that would typically use an H4v6 range, use IPv6prefix31/64. +- Randomize the L4 source port of the inner/middle header (the header immediately following the outer header) for each flow type being tested. +- Test Execution: + - Initiate each flow type sequentially. + - Perform the applicable verification steps for each flow. + - Repeat this process for all flow types, applying the modified field as described above. +- Validation: + - Expected Outcome: The traffic should not be load-balanced; instead, it would simply forwarded along a single path. + - Port traffic counters will be leveraged to verify this behavior. + - Failure Condition: If traffic distribution is observed across multiple paths, the test fails. + +### PF-1.22.5: Verify the Immediate next header's L3 fields are not considered in Load-Balancing Algorithm +- Set up the DUT and ATE as previously specified. +- For each flow type, configure a single destination IP address for both the inner and middle IPv4 and IPv6 headers. Specifically: + - For IPv4 flows that would typically use an H1v4 range, use IPv4prefix1/24. + - For IPv6 flows that would typically use an H1v6 range, use IPv6prefix1/64. + - For IPv4 flows that would typically use an H2v4 range, use IPv4prefix11/24. + - For IPv6 flows that would typically use an H2v6 range, use IPv6prefix11/64. + - For IPv4 flows that would typically use an H4v4 range, use IPv4prefix31/24. + - For IPv6 flows that would typically use an H4v6 range, use IPv6prefix31/64. +- For each flow type, configure a source IP addresses for both the inner and middle IPv4 and IPv6 headers. Specifically: + - For IPv4 flows that would typically use an H1v4 addresses, continue to use the range IPv4prefix1-10/24. + - For IPv6 flows that would typically use an H1v6 addresses, continue to use the range IPv6prefix1-10/64. + - For IPv4 flows that would typically use an ATE1LO1v4 IPv4 address, use the range ATE1LO[1-10] addresses for each applicable flow-type. +- Randomize the L4 source port of the inner/middle header (the header immediately following the outer header) for each flow type being tested. +- Test Execution: + - Initiate each flow type sequentially. + - Perform the applicable verification steps for each flow. + - Repeat this process for all flow types, applying the modified field as described above. +- Validation: + - Expected Outcome: The traffic should not be load-balanced; instead, it would simply forwarded along a single path. + - Port traffic counters will be leveraged to verify this behavior. + - Failure Condition: If traffic distribution is observed across multiple paths, the test fails. + +## Canonical OpenConfig for GUEv1 Decapsulation configuration +TODO: decap policy to be updated by https://github.com/openconfig/public/pull/1288 + +```json +{ + "network-instances": { + "network-instance": { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "policy-forwarding": { + "policies": { + "policy": [ + { + "config": { + "policy-id": "decap-policy" + }, + "rules": { + "rule": [ + { + "sequence-id": 1, + "config": { + "sequence-id": 1 + }, + "ipv4": { + "config": { + "destination-address-prefix-set": "dst_prefix", + "protocol": "IP_UDP" + } + }, + "transport": { + "config": { + "destination-port": 6080 + } + } + "action": { + "decapsulate-gue": true + }, + }, + ] + } + } + ] + } + } + } + } +} + +``` + +## OpenConfig Path and RPC Coverage +```yaml +paths: + +/network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/ipv4/config/destination-address-prefix-set: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/ipv4/config/protocol: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/config/destination-port: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/decapsulate-gue: + +# telemetry +/interfaces/interface/state/counters/out-pkts: +/interfaces/interface/state/counters/out-unicast-pkts: +/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/state/dropped-bytes-aggregate: +/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/state/dropped-aggregate: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` +## Required DUT platform +* Specify the minimum DUT-type: + * FFF - fixed form factor diff --git a/feature/policy_forwarding/decapsulation/otg_tests/ipv4_guev1_decap_and_hashing_test/metadata.textproto b/feature/policy_forwarding/decapsulation/otg_tests/ipv4_guev1_decap_and_hashing_test/metadata.textproto new file mode 100644 index 00000000000..9bdcc576edc --- /dev/null +++ b/feature/policy_forwarding/decapsulation/otg_tests/ipv4_guev1_decap_and_hashing_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "55c4eb1d-a363-44b9-94df-a9e73c9e1234" +plan_id: "PF-1.22" +description: "GUEv1 Decapsulation and ECMP test for IPv4 and IPv6 payload" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/README.md b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/README.md index b71df8ef5ec..35a1403fbc3 100644 --- a/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/README.md +++ b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/README.md @@ -137,7 +137,6 @@ Traffic: Verification: - Traffic is forwarded to ATE Port 2 unchanged. - ## OpenConfig Path and RPC Coverage ```yaml diff --git a/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/decap_gre_ipv4_test.go b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/decap_gre_ipv4_test.go new file mode 100644 index 00000000000..1db8582b4d2 --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/decap_gre_ipv4_test.go @@ -0,0 +1,740 @@ +// Copyright 2023 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 decap_gre_ipv4_test + +import ( + "os" + "strings" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygot/ygot" +) + +const ( + decapDesIpv4IP = "203.0.113.1/24" + decapGrpName = "Decap1" + nullRoute = "Null0" + IPv4Dst1 = "192.0.4.0/30" + IPv6Dst1 = "2001:db8:1:1:198:18:1:0/126" + LBL1 = 3400 + IPv4Dst2 = "192.0.5.0/30" + IPv6Dst2 = "2001:db8:1:1:198:18:2:0/126" + plen6 = 126 + ttlBeforeDecap = 64 + trafficFrameSize = 1000 + trafficPps = 100 + sleepTime = time.Duration(trafficFrameSize / trafficPps) +) + +var ( + dutPort1 = &attrs.Attributes{ + Desc: "dutPort1", + MAC: "00:00:a1:a1:a1:a1", + IPv6: "2001:db8::1", + IPv4: "192.0.2.1", + IPv4Len: 30, + IPv6Len: plen6, + } + + dutPort2 = &attrs.Attributes{ + Desc: "dutPort2", + MAC: "00:00:b1:b1:b1:b1", + IPv6: "2001:db8::5", + IPv4: "192.0.2.5", + IPv4Len: 30, + IPv6Len: plen6, + } + + otgPort1 = &attrs.Attributes{ + Name: "otgPort1", + MAC: "00:00:01:01:01:01", + IPv6: "2001:db8::2", + IPv4: "192.0.2.2", + IPv4Len: 30, + IPv6Len: plen6, + } + + otgPort2 = &attrs.Attributes{ + Name: "otgPort2", + MAC: "00:00:02:02:02:02", + IPv6: "2001:db8::6", + IPv4: "192.0.2.6", + IPv4Len: 30, + IPv6Len: plen6, + } + + otgPorts = map[string]*attrs.Attributes{ + "port1": otgPort1, + "port2": otgPort2, + } + + dutPorts = map[string]*attrs.Attributes{ + "port1": dutPort1, + "port2": dutPort2, + } + + decapDestIp = strings.Split(decapDesIpv4IP, "/")[0] +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// Topology: +// ``` +// | | +// [ ATE Port 1 ] ---- | DUT | ---- [ ATE Port 2 ] +// | | +// ``` +// +// README FILE: +// https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/README.md + +func TestDecapGre(t *testing.T) { + dut := ondatra.DUT(t, "dut") + // configure interfaces on DUT + configureDUT(t, dut) + + ate := ondatra.ATE(t, "ate") + otgConfig := ate.OTG() + config := configureOTG(t, ate) + sfBatch := &gnmi.SetBatch{} + + // Configuring Static Route: DECAP-DST --> Null0 + configStaticRoute(t, dut, "203.0.113.0/24", nullRoute) + + // Configuring Static Route: IPV4-DST1 --> ATE Port 2 + configStaticRoute(t, dut, IPv4Dst1, otgPort2.IPv4) + + // Configuring Static Route: IPV6-DST1 --> ATE Port 2 + configStaticRoute(t, dut, IPv6Dst1, otgPort2.IPv6) + + // Configure Static Route: MPLS label binding + cfgplugins.MPLSStaticLSP(t, sfBatch, dut, "lsp1", LBL1, otgPort2.IPv4, dut.Port(t, "port2").Name(), "ipv4") + + // Configure Static Route: IPV4-DST2 --> ATE Port 2 + configStaticRoute(t, dut, IPv4Dst2, otgPort2.IPv4) + + // Configuring Static Route: IPV6-DST2 --> ATE Port 2 + configStaticRoute(t, dut, IPv6Dst2, otgPort2.IPv6) + + // Policy Based Forwading Rule-1 + cfgplugins.PolicyForwardingGreDecapsulation(t, sfBatch, dut, strings.Split(decapDesIpv4IP, "/")[0], "PBR-Policy", "port1", decapGrpName) + + // Test cases. + type testCase struct { + Name string + Description string + testFunc func(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) + } + + testCases := []testCase{ + { + Name: "Testcase-GreDecapIPv4Traffic", + Description: "GRE Decapsulation of IPv4 traffic", + testFunc: testGreDecapIPv4, + }, + { + Name: "Testcase-GreDecapIPv6Traffic", + Description: "GRE Decapsulation of IPv6 traffic", + testFunc: testGreDecapIPv6, + }, + { + Name: "Testcase-GreDecapIPv4MPLSTraffic", + Description: "GRE Decapsulation of IPv4-over-MPLS traffic", + testFunc: testGreDecapIPv4MPLS, + }, + { + Name: "Testcase-GreDecapIPv6MPLSTraffic", + Description: "GRE Decapsulation of IPv6-over-MPLS traffic", + testFunc: testGreDecapIPv6MPLS, + }, + { + Name: "Testcase-GreDecapMultiLabelMPLSTraffic", + Description: "GRE Decapsulation of multi-label MPLS traffic", + testFunc: testGreDecapMultiLabelMPLS, + }, + { + Name: "Testcase-GrePassthroughTraffic", + Description: "GRE Pass-through (Negative)", + testFunc: testGrePassthrough, + }, + } + + // Run the test cases. + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + t.Logf("Description: %s", tc.Description) + tc.testFunc(t, dut, otgConfig, config) + }) + } + +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + p1 := dut.Port(t, "port1") + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(p1, dutPort1, dut)) + p2 := dut.Port(t, "port2") + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(p2, dutPort2, dut)) + + // Configure Network instance type on DUT + t.Log("Configure/update Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) + } + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + fptest.SetPortSpeed(t, p2) + } +} + +// Configures the given DUT interface. +func configInterfaceDUT(p *ondatra.Port, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + i := a.NewOCInterface(p.Name(), dut) + s4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + + return i +} + +// Congigure Static Routes on DUT +func configStaticRoute(t *testing.T, dut *ondatra.DUTDevice, prefix string, nexthop string) { + b := &gnmi.SetBatch{} + if nexthop == "Null0" { + nexthop = "DROP" + } + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: prefix, + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(nexthop), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) +} + +// Configuration on OTG +func configureOTG(t *testing.T, otg *ondatra.ATEDevice) gosnappi.Config { + otgConfig := gosnappi.NewConfig() + + for portName, portAttrs := range otgPorts { + port := otg.Port(t, portName) + dutPort := dutPorts[portName] + portAttrs.AddToOTG(otgConfig, port, dutPort) + } + + return otgConfig +} + +func enableCapture(t *testing.T, config gosnappi.Config, port string) { + + config.Captures().Clear() + t.Log("Enabling capture on ", port) + config.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) + +} + +func startCapture(t *testing.T, otg *otg.OTG) gosnappi.ControlState { + t.Helper() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) + + return cs +} + +func stopCapture(t *testing.T, otg *otg.OTG, cs gosnappi.ControlState) { + t.Helper() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, cs) +} + +func createFlow(flowName string, destMac string) gosnappi.Flow { + flow := gosnappi.NewFlow().SetName(flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{otgPort1.Name + ".IPv4"}).SetRxNames([]string{otgPort2.Name + ".IPv4"}) + flow.Size().SetFixed(trafficFrameSize) + flow.Rate().SetPps(trafficPps) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(destMac) + outerIPHeader := flow.Packet().Add().Ipv4() + outerIPHeader.Src().SetValue(otgPort1.IPv4) + outerIPHeader.Dst().SetValue(decapDestIp) + outerIPHeader.TimeToLive().SetValue(ttlBeforeDecap) + + return flow +} + +func testGreDecapIPv4(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) { + config.Flows().Clear() + flow := createFlow("DecapGREIpv4", otgPort1.MAC) + + flow.Packet().Add().Gre() + + grev4 := flow.Packet().Add().Ipv4() + grev4.Src().SetValue(otgPort1.IPv4) + staticIp := strings.Split(IPv4Dst1, "/")[0] + grev4.Src().SetValue(otgPort1.IPv4) + grev4.Dst().SetValue(staticIp) + grev4.Priority().Dscp().Phb().SetValue(32) + + config.Flows().Append(flow) + + enableCapture(t, config, "port2") + otgConfig.PushConfig(t, config) + otgConfig.StartProtocols(t) + + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + otgutils.WaitForARP(t, otgConfig, config, "IPv6") + + cs := startCapture(t, otgConfig) + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + + otgConfig.StopTraffic(t) + + stopCapture(t, otgConfig, cs) + + validateDUTPkts(t, dut) + validateTrafficLoss(t, otgConfig, config, "DecapGREIpv4") + validateGREDecapIpv4(t, otgConfig, false) +} + +func testGreDecapIPv6(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) { + config.Flows().Clear() + flow := createFlow("DecapIpv6GRE", otgPort1.MAC) + + flow.Packet().Add().Gre() + + grev6 := flow.Packet().Add().Ipv6() + grev6.Src().SetValue(otgPort1.IPv4) + + staticIp := strings.Split(IPv6Dst1, "/")[0] + grev6.Src().SetValue(otgPort1.IPv6) + grev6.Dst().SetValue(staticIp) + grev6.TrafficClass().SetValue(128) + + config.Flows().Append(flow) + otgConfig.PushConfig(t, config) + + enableCapture(t, config, "port2") + otgConfig.StartProtocols(t) + + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + otgutils.WaitForARP(t, otgConfig, config, "IPv6") + + cs := startCapture(t, otgConfig) + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + + otgConfig.StopTraffic(t) + + stopCapture(t, otgConfig, cs) + validateTrafficLoss(t, otgConfig, config, "DecapIpv6GRE") + validateGREDecapIpv6(t, otgConfig, false) + +} + +func testGreDecapIPv4MPLS(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) { + config.Flows().Clear() + flow := createFlow("Decap-IPv4-over-MPLS", otgPort1.MAC) + + flow.Packet().Add().Gre() + + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(LBL1) + + grev4 := flow.Packet().Add().Ipv4() + grev4.Src().SetValue(otgPort1.IPv4) + + staticIp := strings.Split(IPv4Dst1, "/")[0] + grev4.Src().SetValue(otgPort1.IPv4) + grev4.Dst().SetValue(staticIp) + grev4.Priority().Dscp().Phb().SetValue(32) + + config.Flows().Append(flow) + otgConfig.PushConfig(t, config) + + enableCapture(t, config, "port2") + + otgConfig.StartProtocols(t) + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + otgutils.WaitForARP(t, otgConfig, config, "IPv6") + + cs := startCapture(t, otgConfig) + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + + otgConfig.StopTraffic(t) + + stopCapture(t, otgConfig, cs) + validateTrafficLoss(t, otgConfig, config, "Decap-IPv4-over-MPLS") + validateGREDecapIpv4(t, otgConfig, true) + +} + +func testGreDecapIPv6MPLS(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) { + config.Flows().Clear() + flow := createFlow("Decap-IPv6-over-MPLS", otgPort1.MAC) + + flow.Packet().Add().Gre() + + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(LBL1) + + grev6 := flow.Packet().Add().Ipv6() + grev6.Src().SetValue(otgPort1.IPv4) + + staticIp := strings.Split(IPv6Dst1, "/")[0] + grev6.Src().SetValue(otgPort1.IPv6) + grev6.Dst().SetValue(staticIp) + grev6.TrafficClass().SetValue(128) + + config.Flows().Append(flow) + otgConfig.PushConfig(t, config) + + enableCapture(t, config, "port2") + + otgConfig.StartProtocols(t) + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + otgutils.WaitForARP(t, otgConfig, config, "IPv6") + + cs := startCapture(t, otgConfig) + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + + otgConfig.StopTraffic(t) + + stopCapture(t, otgConfig, cs) + validateTrafficLoss(t, otgConfig, config, "Decap-IPv6-over-MPLS") + validateGREDecapIpv6(t, otgConfig, true) + +} + +func testGreDecapMultiLabelMPLS(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) { + config.Flows().Clear() + flow := createFlow("Decap-Multilabel-MPLS", otgPort1.MAC) + + flow.Packet().Add().Gre() + + mplsL1 := flow.Packet().Add().Mpls() + mplsL1.Label().SetValue(LBL1) + mplsL1.TrafficClass().SetValue(4) + + mplsL2 := flow.Packet().Add().Mpls() + mplsL2.Label().SetValue(3500) + mplsL2.TrafficClass().SetValue(4) + + grev4 := flow.Packet().Add().Ipv4() + grev4.Src().SetValue(otgPort1.IPv4) + + staticIp := strings.Split(IPv4Dst1, "/")[0] + grev4.Src().SetValue(otgPort1.IPv4) + grev4.Dst().SetValue(staticIp) + grev4.Priority().Dscp().Phb().SetValue(32) + + config.Flows().Append(flow) + otgConfig.PushConfig(t, config) + + enableCapture(t, config, "port2") + + otgConfig.StartProtocols(t) + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + otgutils.WaitForARP(t, otgConfig, config, "IPv6") + + cs := startCapture(t, otgConfig) + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + + otgConfig.StopTraffic(t) + + stopCapture(t, otgConfig, cs) + validateTrafficLoss(t, otgConfig, config, "Decap-Multilabel-MPLS") + validateMPLSDecapTraffic(t, otgConfig) + +} + +func testGrePassthrough(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config) { + config.Flows().Clear() + + flow := gosnappi.NewFlow().SetName("DecapGREPassthrough") + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{otgPort1.Name + ".IPv4"}).SetRxNames([]string{otgPort2.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(otgPort1.MAC) + + outerIPHeader := flow.Packet().Add().Ipv4() + outerIPHeader.Src().SetValue(otgPort1.IPv4) + staticIp := strings.Split(IPv4Dst1, "/")[0] + outerIPHeader.Dst().SetValue(staticIp) + + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(otgPort1.IPv6) + ipv6Ip := strings.Split(IPv6Dst2, "/")[0] + v6.Dst().SetValue(ipv6Ip) + + flow.Packet().Add().Gre() + + config.Flows().Append(flow) + otgConfig.PushConfig(t, config) + + otgConfig.StartProtocols(t) + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + otgutils.WaitForARP(t, otgConfig, config, "IPv6") + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + + otgConfig.StopTraffic(t) + + validateTrafficLoss(t, otgConfig, config, "DecapGREPassthrough") +} + +func validateGREDecapIpv4(t *testing.T, otgConfig *otg.OTG, checkTTL bool) { + packetCaptureGRE := processCapture(t, otgConfig, "port2") + handle, err := pcap.OpenOffline(packetCaptureGRE) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + packet := <-packetSource.Packets() + + var ipv4Layer *layers.IPv4 + ipv4LayerCount := 0 + for _, layer := range packet.Layers() { + if ipLayer, ok := layer.(*layers.IPv4); ok { + ipv4LayerCount++ + ipv4Layer = ipLayer + } + } + + if ipv4LayerCount == 2 { + t.Fatalf("Error: Decapsulated IPv4 traffic is not received. Expected %v, got %v", "1", ipv4LayerCount) + } + + if ipv4Layer != nil { + dscpValue := ipv4Layer.TOS >> 2 + if dscpValue != 32 { + t.Fatalf("Error: Inner-packet DSCP should be preserved. Expected: 32, Got: %v", dscpValue) + } else { + t.Logf("Inner-packet DSCP is preserved. Expected: 32, Got: %v", dscpValue) + } + if checkTTL { + expectedttl := uint32(ttlBeforeDecap - 1) + got := uint32(ipv4Layer.TTL) + if got != expectedttl { + t.Fatalf("TTL mismatch, got: %d, want: %d", got, expectedttl) + } else { + t.Logf("Got expected TTL") + } + } + } +} + +func validateGREDecapIpv6(t *testing.T, otgConfig *otg.OTG, checkTTL bool) { + capturePktFile := processCapture(t, otgConfig, "port2") + handle, err := pcap.OpenOffline(capturePktFile) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + + for packet := range packetSource.Packets() { + if ipv4Layer := packet.Layer(layers.LayerTypeIPv4); ipv4Layer != nil { + t.Fatalf("Error: Decapsulated IPv6 traffic is not received on ATE Port 2") + } + if ipv6Layer := packet.Layer(layers.LayerTypeIPv6); ipv6Layer != nil { + ipv6, _ := ipv6Layer.(*layers.IPv6) + t.Logf("Decapsulated IPv6 traffic is received on ATE Port 2") + if trafficClass := ipv6.TrafficClass; trafficClass != 128 { + t.Fatalf("Error: Inner-packet traffic-class is not preserved. Expected: 128, Got: %v", trafficClass) + } else { + t.Logf("Inner-packet traffic-class is preserved. Expected: 128, Got: %v", trafficClass) + } + + if checkTTL { + expectedttl := uint32(ttlBeforeDecap - 1) + got := uint32(ipv6.HopLimit) + if got != expectedttl { + t.Fatalf("TTL mismatch, got: %d, want: %d", got, expectedttl) + } else { + t.Logf("Got expected TTL") + } + } + break + } else { + t.Fatalf("Decapsulated IPv6 traffic is not received on ATE Port 2") + } + } + +} + +func validateMPLSDecapTraffic(t *testing.T, otgConfig *otg.OTG) { + capturePktFile := processCapture(t, otgConfig, "port2") + handle, err := pcap.OpenOffline(capturePktFile) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + packet := <-packetSource.Packets() + + var mplsLayer *layers.MPLS + mplsLayerCount := 0 + + for _, layer := range packet.Layers() { + if ipLayer, ok := layer.(*layers.MPLS); ok { + mplsLayerCount++ + mplsLayer = ipLayer + } + } + + if mplsLayerCount == 2 { + t.Fatalf("Error: Decapsulated MPLS traffic is not received. Expected %v, got %v", "1", mplsLayerCount) + } + + if mplsLayer != nil { + expectedttl := uint32(ttlBeforeDecap - 1) + got := uint32(mplsLayer.TTL) + if got != expectedttl { + t.Fatalf("TTL mismatch, got: %d, want: %d", got, expectedttl) + } else { + t.Logf("Got expected TTL") + } + + mplsExp := mplsLayer.TrafficClass + if mplsExp != 4 { + t.Fatalf("Error: EXP is not set to original value.. Expected: 4, Got: %v", mplsExp) + } else { + t.Logf("Got expected EXP.Expected: 4, Got: %v", mplsExp) + } + } +} + +func validateTrafficLoss(t *testing.T, otgConfig *otg.OTG, config gosnappi.Config, flowName string) { + otgutils.LogFlowMetrics(t, otgConfig, config) + otgutils.LogPortMetrics(t, otgConfig, config) + + outPkts := float32(gnmi.Get(t, otgConfig, gnmi.OTG().Flow(flowName).Counters().OutPkts().State())) + inPkts := float32(gnmi.Get(t, otgConfig, gnmi.OTG().Flow(flowName).Counters().InPkts().State())) + t.Logf("outPkts: %v, inPkts: %v", outPkts, inPkts) + if outPkts == 0 { + t.Fatalf("OutPkts for flow %s is 0, want > 0", flowName) + } + if got := ((outPkts - inPkts) * 100) / outPkts; got > 0 { + t.Fatalf("LossPct for flow %s: got %v, want 0", flowName, got) + } +} + +func processCapture(t *testing.T, otg *otg.OTG, port string) string { + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(port)) + time.Sleep(30 * time.Second) + capturePktFile, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := capturePktFile.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + capturePktFile.Close() + return capturePktFile.Name() +} + +// TO-DO: Curently PolicyForwarding not supported in DUT. Adding deviation to check the PF counters. +// Currently added the workaround through CLI, to check the number of packets on the DUT. +func validateDUTPkts(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.GreDecapsulationOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + port1 := dut.Port(t, "port1") + ingressPort := port1.Name() + + port2 := dut.Port(t, "port2") + egressPort := port2.Name() + + ingressPktPath := gnmi.OC().Interface(ingressPort).Counters().InUnicastPkts().State() + ingressPkt, present := gnmi.Lookup(t, dut, ingressPktPath).Val() + if !present { + t.Errorf("Get IsPresent status for path %q: got false, want true", ingressPktPath) + } + + egressPktPath := gnmi.OC().Interface(egressPort).Counters().InUnicastPkts().State() + egressPkt, present := gnmi.Lookup(t, dut, egressPktPath).Val() + if !present { + t.Errorf("Get IsPresent status for path %q: got false, want true", egressPktPath) + } + + if ingressPkt == 0 || egressPkt == 0 { + t.Errorf("Got the unexpected packet count ingressPkt: %d, egreesPkt: %d", ingressPkt, egressPkt) + } + + if ingressPkt > egressPkt { + t.Logf("Interface counters reflect decapsulated packets.") + } else { + t.Errorf("Error: Interface counters didn't reflect decapsulated packets.") + } + default: + t.Errorf("Deviation GreDecapsulationUnsupported is not handled for the dut: %v", dut.Vendor()) + } + } else { + // TO-DO: Once the support is added in the DUT, need to work on the validation of PF counters. + matchedPkts := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy("PBR-MAP").Rule(10).MatchedPkts() + pktCount := gnmi.Get(t, dut, matchedPkts.State()) + if pktCount != 0 { + t.Logf("Interface counters received") + } else { + t.Errorf("Counters received is 0") + } + + matchedOctets := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy("PBR-MAP").Rule(10).MatchedOctets() + octetCount := gnmi.Get(t, dut, matchedOctets.State()) + if octetCount == 0 { + t.Errorf("Octet count is 0") + } + } + +} diff --git a/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/metadata.textproto b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/metadata.textproto new file mode 100644 index 00000000000..8237b8cdb9b --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/decap_gre_ipv4/metadata.textproto @@ -0,0 +1,19 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "dc18d70e-7aae-4292-bdf1-c99fd6ed8e79" +plan_id: "PF-1.3" +description: "Policy-based IPv4 GRE Decapsulation" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + static_protocol_name: "STATIC" + interface_enabled: true + default_network_instance: "default" + static_mpls_lsp_oc_unsupported: true + gre_decapsulation_oc_unsupported: true + } +} \ No newline at end of file diff --git a/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/README.md b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/README.md index fc7ba59b02a..c31869c8c96 100644 --- a/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/README.md +++ b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/README.md @@ -48,7 +48,7 @@ The test verifies policy forwarding(PF) encapsulation action to IPv4 GRE tunnel Match and remark all values for 3 leftmost DSCP bits [0, 8, 16, 24, 32, 40, 48, 56]. -### PF-1.1.1: Verify PF GRE encapsulate action for IPv4 traffic +### PF-1.2.1: Verify PF GRE encapsulate action for IPv4 traffic Generate traffic on ATE Port 1 from IPV4-SRC2 from a random combination of 1000 source addresses to IPV4-DST at linerate. Use 512 bytes frame size. @@ -59,7 +59,7 @@ Verify: * Traffic equally load-balanced across 32 GRE destinations. * Verify PF packet counters matching traffic generated. -### PF-1.1.2: Verify PF GRE encapsulate action for IPv6 traffic +### PF-1.2.2: Verify PF GRE encapsulate action for IPv6 traffic Generate traffic on ATE Port 1 from IPV6-SRC2 from a random combination of 1000 source addresses to IPV6-DST. Use 512 bytes frame size. @@ -70,7 +70,7 @@ Verify: * Traffic equally load-balanced across 32 GRE destinations. * Verify PF packet counters matching traffic generated. -### PF-1.1.3: Verify PF IPV4 forward action +### PF-1.2.3: Verify PF IPV4 forward action Generate traffic on ATE Port 1 from sources IPV4-SRC1 to IPV4-DST. Verify: @@ -78,7 +78,7 @@ Verify: * All traffic received on ATE Port 3. * No packet loss when forwarding. -### PF-1.1.4: Verify PF IPV6 forward action +### PF-1.2.4: Verify PF IPV6 forward action Generate traffic on ATE Port 1 from sources IPV6-SRC1 to IPV6-DST. Verify: @@ -86,29 +86,97 @@ Verify: * All traffic received on ATE Port 3. * No packet loss when forwarding. -### PF-1.1.5: Verify PF GRE DSCP copy to outer header for IPv4 traffic +### PF-1.2.5: Verify PF GRE DSCP copy to outer header for IPv4 traffic Generate traffic on ATE Port 1 from IPV4-SRC1 source for every DSCP value in [0, 8, 16, 24, 32, 40, 48, 56] Verify: * All traffic received on ATE Port 2 GRE-encapsulated. * Outer GRE IPv4 header has same marking as ingress non-encapsulated IPv4 packet. +* Verify that DSCP in inner IPv4 header is unchanged. -### PF-1.1.6: Verify PF GRE DSCP copy to outer header for IPv6 traffic +### PF-1.2.6: Verify PF GRE DSCP copy to outer header for IPv6 traffic Generate traffic on ATE Port 1 from IPV6-SRC1 for every IPv6 TC 8-bit value [0, 32, 64, 96, 128, 160, 192, 224] Verify: * All traffic received on ATE Port 2 GRE-encapsulated. * Outer GRE IPv4 header has DSCP match to ingress IPv6 TC packet. - -### PF-1.1.7: Verify MTU handling during GRE encap -* Generate traffic on ATE Port 1 from IPV4-SRC1 with frame size of 4000 with DF-bit set. -* Generate traffic on ATE Port 1 from IPV6-SRC1 with frame size of 4000 with DF-bit set. - -Verify: - -* DUT generates "Fragmentation Needed" message back to ATE source. +* Verify that TC in inner IPv6 header is unchanged. +## Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "Customer A", + "name": "eth0" + }, + "name": "eth0" + } + ] + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "policy-forwarding": { + "interfaces": { + "interface": [ + { + "config": { + "apply-forwarding-policy": "Encap GRE", + "interface-id": "eth0" + }, + "interface-id": "eth0" + } + ] + }, + "policies": { + "policy": [ + { + "config": { + "policy-id": "Encap GRE" + }, + "policy-id": "Encap GRE", + "rules": { + "rule": [ + { + "action": { + "encapsulate-gre": { + "targets": { + "target": [ + { + "config": { + "destination": "10.10.10.1/32", + "id": "Customer A" + }, + "id": "Customer A" + } + ] + } + } + }, + "config": { + "sequence-id": 1 + }, + "sequence-id": 1 + } + ] + } + } + ] + } + } + } + ] + } +} +``` ## OpenConfig Path and RPC Coverage @@ -142,4 +210,4 @@ rpcs: ## Required DUT platform * MFF -* FFF \ No newline at end of file +* FFF diff --git a/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/encap_gre_ipv4_test.go b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/encap_gre_ipv4_test.go new file mode 100644 index 00000000000..8db85429317 --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/encap_gre_ipv4_test.go @@ -0,0 +1,698 @@ +package encap_gre_ipv4_test + +import ( + "fmt" + "os" + "strconv" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/qoscfg" + "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" +) + +const ( + trafficFrameSize = 512 + trafficRatePps = 1000 + noOfPackets = 1024 + greProtocol = 47 + trafficPolicyName = "IP_MATCH_TRAFFIC_POLICY" + tunnelCount = 32 + captureFilePath = "/tmp/capture.pcap" + testTimeout = 10 * time.Second + ipv4 = "IPv4" + ipv6 = "IPv6" + ipv4PrefixLen = 32 + ipv6PrefixLen = 128 + nhGroupName = "SRC_NH" + port1 = "port1" + port2 = "port2" + port3 = "port3" +) + +var ( + defaultLoopbackAttrs = attrs.Attributes{ + Desc: "Default loopback interface attributes", + IPv4: "192.0.20.2", + IPv4Len: 32, + } + + dutLoopbackAttrs attrs.Attributes + + ruleSequenceMap = map[string]uint8{ + "rule-src1-v4": 1, + "rule-src1-v6": 2, + "rule-src2-v4": 3, + "rule-src2-v6": 4, + } + + ruleMatchedPackets = map[string]uint64{ + "rule-src1-v4": 0, + "rule-src1-v6": 0, + "rule-src2-v4": 0, + "rule-src2-v6": 0, + } + + dutPort1 = attrs.Attributes{Desc: "Dut port 1", IPv4: "192.0.2.1", IPv4Len: 30, IPv6: "2001:DB8:0::1", IPv6Len: 126} + dutPort2 = attrs.Attributes{Desc: "Dut port 2", IPv4: "192.0.2.5", IPv4Len: 30, IPv6: "2001:DB8:0::5", IPv6Len: 126} + dutPort3 = attrs.Attributes{Desc: "Dut port 3", IPv4: "192.0.2.9", IPv4Len: 30, IPv6: "2001:DB8:0::9", IPv6Len: 126} + + otgPort1 = attrs.Attributes{Desc: "OTG port 1", Name: port1, MAC: "00:01:12:00:00:01", IPv4: "192.0.2.2", IPv4Len: 30, IPv6: "2001:DB8:0::2", IPv6Len: 126, MTU: 9216} + otgPort2 = attrs.Attributes{Desc: "OTG port 1", Name: port2, MAC: "00:01:12:00:00:02", IPv4: "192.0.2.6", IPv4Len: 30, IPv6: "2001:DB8:0::6", IPv6Len: 126, MTU: 2000} + otgPort3 = attrs.Attributes{Desc: "OTG port 1", Name: port3, MAC: "00:01:12:00:00:03", IPv4: "192.0.2.10", IPv4Len: 30, IPv6: "2001:DB8:0::A", IPv6Len: 126, MTU: 2000} + + tunnelDestinations = []string{} + dscpValues = []uint8{0, 8, 16, 24, 32, 40, 48, 56} + classifierName = "qos-classifier-1" +) + +type ipFlow interface { + HasSrc() bool + HasDst() bool +} + +type testCase struct { + name string + ipType string + capturePort string + captureFilename string + srcDstPortPair []attrs.Attributes + applyCustomFlow func(t *testing.T, top *gosnappi.Config, tc testCase, flow *gosnappi.Flow, packet *ipFlow) + verifyOutput func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) + checkEncapDscp bool + checkEncapLoadBalanced bool + flowName string + policyRule string +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestEncapGREIPv4(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + top := gosnappi.NewConfig() + configureTunnelDestinations() + configureDUT(t, dut) + + ap1 := ate.Port(t, port1) + ap2 := ate.Port(t, port2) + ap3 := ate.Port(t, port3) + + otgPort1.AddToOTG(top, ap1, &dutPort1) + otgPort2.AddToOTG(top, ap2, &dutPort2) + otgPort3.AddToOTG(top, ap3, &dutPort3) + + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + testCases := []testCase{ + { + name: "PF-1.1.1: Verify PF GRE encapsulate action for IPv4 traffic", + ipType: ipv4, + capturePort: port2, + srcDstPortPair: []attrs.Attributes{otgPort1, otgPort2}, + applyCustomFlow: func(t *testing.T, top *gosnappi.Config, tc testCase, flow *gosnappi.Flow, packet *ipFlow) { + foundIpv4, ok := (*packet).(gosnappi.FlowIpv4) + if !ok || foundIpv4 == nil { + return + } + foundIpv4.Src().Increment().SetStart(tc.srcDstPortPair[0].IPv4).SetStep("0.0.0.1").SetCount(1000) + }, + verifyOutput: func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) { + checkGreCapture(t, tc) + checkPolicyStatistics(t, dut, tc) + }, + checkEncapDscp: false, + checkEncapLoadBalanced: true, + policyRule: "rule-src1-v4", + flowName: "FlowTC1", + }, + { + name: "PF-1.1.2: Verify PF GRE encapsulate action for IPv6 traffic", + ipType: ipv6, + capturePort: port2, + srcDstPortPair: []attrs.Attributes{otgPort1, otgPort2}, + applyCustomFlow: func(t *testing.T, top *gosnappi.Config, tc testCase, flow *gosnappi.Flow, packet *ipFlow) { + foundIpv6, ok := (*packet).(gosnappi.FlowIpv6) + if !ok || foundIpv6 == nil { + return + } + foundIpv6.Src().Increment().SetStart(tc.srcDstPortPair[0].IPv6).SetStep("::1").SetCount(1000) + }, + verifyOutput: func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) { + checkGreCapture(t, tc) + checkPolicyStatistics(t, dut, tc) + }, + checkEncapDscp: false, + checkEncapLoadBalanced: true, + policyRule: "rule-src1-v6", + flowName: "FlowTC2", + }, + { + name: "PF-1.1.3: Verify PF IPV4 forward action", + ipType: ipv4, + srcDstPortPair: []attrs.Attributes{otgPort1, otgPort3}, + applyCustomFlow: nil, + verifyOutput: func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) { + checkFlowStats(t, ate, tc) + checkPolicyStatistics(t, dut, tc) + }, + checkEncapDscp: false, + checkEncapLoadBalanced: false, + policyRule: "rule-src2-v4", + flowName: "FlowTC3", + }, + { + name: "PF-1.1.4: Verify PF IPV6 forward action", + ipType: ipv6, + srcDstPortPair: []attrs.Attributes{otgPort1, otgPort3}, + applyCustomFlow: nil, + verifyOutput: func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) { + checkFlowStats(t, ate, tc) + checkPolicyStatistics(t, dut, tc) + }, + checkEncapDscp: false, + checkEncapLoadBalanced: false, + policyRule: "rule-src2-v6", + flowName: "FlowTC4", + }, + { + name: "PF-1.1.5: Verify PF GRE DSCP copy to outer header for IPv4 traffic", + ipType: ipv4, + capturePort: port2, + srcDstPortPair: []attrs.Attributes{otgPort1, otgPort2}, + applyCustomFlow: func(t *testing.T, top *gosnappi.Config, tc testCase, flow *gosnappi.Flow, packet *ipFlow) { + foundIpv4, ok := (*packet).(gosnappi.FlowIpv4) + if !ok || foundIpv4 == nil { + return + } + + var dscpValues32 []uint32 + for _, v := range dscpValues { + dscpValues32 = append(dscpValues32, uint32(v)) + } + foundIpv4.Priority().Dscp().Phb().SetValues(dscpValues32) + }, + verifyOutput: func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) { + checkGreCapture(t, tc) + checkPolicyStatistics(t, dut, tc) + }, + checkEncapDscp: true, + checkEncapLoadBalanced: false, + policyRule: "rule-src1-v4", + flowName: "FlowTC5", + }, + { + name: "PF-1.1.6: Verify PF GRE DSCP copy to outer header for IPv6 traffic", + ipType: ipv6, + capturePort: port2, + srcDstPortPair: []attrs.Attributes{otgPort1, otgPort2}, + applyCustomFlow: func(t *testing.T, top *gosnappi.Config, tc testCase, flow *gosnappi.Flow, packet *ipFlow) { + foundIpv6, ok := (*packet).(gosnappi.FlowIpv6) + if !ok || foundIpv6 == nil { + return + } + + var tcValues []uint32 + for _, dscp := range dscpValues { + tcValues = append(tcValues, uint32(dscp<<2)) + } + foundIpv6.TrafficClass().SetValues(tcValues) + }, + verifyOutput: func(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, tc testCase) { + checkGreCapture(t, tc) + checkPolicyStatistics(t, dut, tc) + }, + checkEncapDscp: true, + checkEncapLoadBalanced: false, + policyRule: "rule-src1-v6", + flowName: "FlowTC6", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runTest(t, tc, dut, ate, top) + }) + } +} + +func configureFlows(t *testing.T, config *gosnappi.Config, tc testCase) { + (*config).Flows().Clear() + flow := (*config).Flows().Add().SetName(tc.flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{fmt.Sprintf("%s.%s", tc.srcDstPortPair[0].Name, tc.ipType)}).SetRxNames([]string{fmt.Sprintf("%s.%s", tc.srcDstPortPair[1].Name, tc.ipType)}) + flow.Size().SetFixed(trafficFrameSize) + flow.Rate().SetPps(trafficRatePps) + flow.Duration().SetFixedPackets(gosnappi.NewFlowFixedPackets().SetPackets(noOfPackets)) + + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(tc.srcDstPortPair[0].MAC) + var ipPacket ipFlow + switch tc.ipType { + case ipv4: + ipv4 := flow.Packet().Add().Ipv4() + ipv4.Src().SetValue(tc.srcDstPortPair[0].IPv4) + ipv4.Dst().SetValue(tc.srcDstPortPair[1].IPv4) + ipPacket = ipv4 + case ipv6: + ipv6 := flow.Packet().Add().Ipv6() + ipv6.Src().SetValue(tc.srcDstPortPair[0].IPv6) + ipv6.Dst().SetValue(tc.srcDstPortPair[1].IPv6) + ipPacket = ipv6 + default: + t.Errorf("Invalid traffic type %s", tc.ipType) + } + + if tc.applyCustomFlow != nil { + tc.applyCustomFlow(t, config, tc, &flow, &ipPacket) + } +} + +func waitForTraffic(t *testing.T, otg *otg.OTG, flowName string, timeout time.Duration) { + transmitPath := gnmi.OTG().Flow(flowName).Transmit().State() + _, ok := gnmi.Watch(t, otg, transmitPath, timeout, func(val *ygnmi.Value[bool]) bool { + transmitState, present := val.Val() + return present && !transmitState + }).Await(t) + + if !ok { + t.Errorf("Traffic for flow %s did not stop within the timeout of %d", flowName, timeout) + } else { + t.Logf("Traffic for flow %s has stopped", flowName) + } +} + +func runTest(t *testing.T, tc testCase, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, config gosnappi.Config) { + var captureState gosnappi.ControlState + configureFlows(t, &config, tc) + + if tc.capturePort != "" { + enableCapture(t, ate, config, []string{tc.capturePort}) + defer clearCapture(t, ate, config) + } + + otg := ate.OTG() + otg.PushConfig(t, config) + otg.StartProtocols(t) + + if tc.capturePort != "" { + captureState = startCapture(t, ate) + } + + otg.StartTraffic(t) + waitForTraffic(t, otg, tc.flowName, testTimeout) + otg.StopProtocols(t) + + if captureState != nil { + stopCapture(t, ate, captureState) + tc.captureFilename = getCapture(t, ate, tc) + } + + otgutils.LogFlowMetrics(t, otg, config) + otgutils.LogPortMetrics(t, otg, config) + + if tc.verifyOutput != nil { + tc.verifyOutput(t, dut, ate, tc) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dp1 := dut.Port(t, port1) + dp2 := dut.Port(t, port2) + dp3 := dut.Port(t, port3) + + interfaceName := dut.Port(t, port1).Name() + + t.Logf("Configuring Interfaces") + configureDUTPort(t, dut, &dutPort1, dp1) + configureDUTPort(t, dut, &dutPort2, dp2) + configureDUTPort(t, dut, &dutPort3, dp3) + + configureLoopbackInterface(t, dut) + + t.Logf("Configuring Hardware Init") + configureHardwareInit(t, dut) + + encapTarget := make(map[string]*oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action_EncapsulateGre_Target) + width := len(fmt.Sprintf("%d", tunnelCount)) + for index, dest := range tunnelDestinations { + key := fmt.Sprintf("gre%0*d", width, index+1) + encapTarget[key] = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action_EncapsulateGre_Target{ + Source: ygot.String(dutLoopbackAttrs.IPv4), + Destination: ygot.String(dest), + } + } + + policyRules := []cfgplugins.PolicyForwardingRule{ + { + Id: 1, + Name: "rule_ipv4_pass", + IpType: ipv4, + DestinationAddress: fmt.Sprintf("%s/%d", otgPort3.IPv4, ipv4PrefixLen), + Action: &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action{ + NextHop: ygot.String(dutPort3.IPv4), + }, + }, + { + Id: 2, + Name: "rule_ipv6_pass", + IpType: ipv6, + DestinationAddress: fmt.Sprintf("%s/%d", otgPort3.IPv6, ipv6PrefixLen), + Action: &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action{ + NextHop: ygot.String(dutPort3.IPv6), + }, + }, + { + Id: 3, + Name: "rule_ipv4_encap", + IpType: ipv4, + DestinationAddress: fmt.Sprintf("%s/%d", otgPort2.IPv4, ipv4PrefixLen), + Action: &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action{ + EncapsulateGre: &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action_EncapsulateGre{ + Target: encapTarget, + }, + }, + }, + { + Id: 4, + Name: "rule_ipv6_encap", + IpType: ipv6, + DestinationAddress: fmt.Sprintf("%s/%d", otgPort2.IPv6, ipv6PrefixLen), + Action: &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action{ + EncapsulateGre: &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action_EncapsulateGre{ + Target: encapTarget, + }, + }, + }, + } + + t.Logf("Configuring Policy Forwarding") + _, ni, pf := cfgplugins.SetupPolicyForwardingInfraOC(deviations.DefaultNetworkInstance(dut)) + cfgplugins.NewPolicyForwardingEncapGre(t, dut, pf, trafficPolicyName, interfaceName, nhGroupName, policyRules) + cfgplugins.ApplyPolicyToInterfaceOC(t, pf, interfaceName, trafficPolicyName) + if !deviations.PolicyForwardingOCUnsupported(dut) { + cfgplugins.PushPolicyForwardingConfig(t, dut, ni) + } + + t.Logf("Configuring QOS") + ipv6DscpValues := []uint8{} + for _, dscp := range dscpValues { + ipv6DscpValues = append(ipv6DscpValues, dscp<<2) + } + qos := &oc.Qos{} + cfgplugins.ConfigureQosClassifierDscpRemark(t, dut, qos, classifierName, interfaceName, dscpValues, ipv6DscpValues) + pushQosClassifierToDUT(t, dut, qos, interfaceName, classifierName, true) + + t.Logf("Configuring Static Routes") + configureStaticRoutes(t, dut) +} + +func configureTunnelDestinations() { + for index := range tunnelCount { + tunnelDestinations = append(tunnelDestinations, fmt.Sprintf("203.0.113.%d", index+10)) + } +} + +func configureHardwareInit(t *testing.T, dut *ondatra.DUTDevice) { + hardwareInitCfg := cfgplugins.NewDUTHardwareInit(t, dut, cfgplugins.FeaturePolicyForwarding) + if hardwareInitCfg == "" { + return + } + cfgplugins.PushDUTHardwareInitConfig(t, dut, hardwareInitCfg) +} + +func configureDUTPort(t *testing.T, dut *ondatra.DUTDevice, attrs *attrs.Attributes, p *ondatra.Port) { + t.Helper() + d := gnmi.OC() + i := attrs.NewOCInterface(p.Name(), dut) + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), i) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + cfgplugins.AssignToNetworkInstance(t, dut, p.Name(), deviations.DefaultNetworkInstance(dut), 0) + } +} + +func configureLoopbackInterface(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + loopbackIntfName := netutil.LoopbackInterface(t, dut, 0) + dutLoopbackAttrs.Name = loopbackIntfName + loopbackIntf := gnmi.OC().Interface(loopbackIntfName).Subinterface(0) + ipv4Addrs := gnmi.LookupAll(t, dut, loopbackIntf.Ipv4().AddressAny().State()) + if len(ipv4Addrs) == 0 { + loopIntf := defaultLoopbackAttrs.NewOCInterface(loopbackIntfName, dut) + loopIntf.Type = oc.IETFInterfaces_InterfaceType_softwareLoopback + gnmi.Update(t, dut, dc.Interface(loopbackIntfName).Config(), loopIntf) + dutLoopbackAttrs.IPv4 = defaultLoopbackAttrs.IPv4 + return + } + + v4, ok := ipv4Addrs[0].Val() + if !ok { + t.Fatalf("Unable to get loopback ipv4 address for %s", loopbackIntfName) + } + dutLoopbackAttrs.IPv4 = v4.GetIp() + t.Logf("Got DUT IPv4 loopback address: %v", dutLoopbackAttrs.IPv4) +} + +func configureStaticRoutes(t *testing.T, dut *ondatra.DUTDevice) { + configStaticRoute(t, dut, "192.0.2.0/30", "192.0.2.10", "0") + for index, dest := range tunnelDestinations { + configStaticRoute(t, dut, fmt.Sprintf("%s/32", dest), otgPort2.IPv4, strconv.Itoa(index+1)) + } + configStaticRoute(t, dut, "2001:DB8:0::0/126", "2001:DB8:0::10", "0") +} + +func configStaticRoute(t *testing.T, dut *ondatra.DUTDevice, prefix string, nexthop string, index string) { + b := &gnmi.SetBatch{} + if nexthop == "Null0" { + nexthop = "DROP" + } + routeCfg := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: prefix, + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + index: oc.UnionString(nexthop), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, routeCfg, dut); err != nil { + t.Fatalf("Failed to configure static route: %v", err) + } + b.Set(t, dut) +} + +func pushQosClassifierToDUT(t *testing.T, dut *ondatra.DUTDevice, qos *oc.Qos, interfaceName, classifierName string, isInputClassifier bool) { + if deviations.QosRemarkOCUnsupported(dut) { + return + } + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), qos) + if isInputClassifier { + qoscfg.SetInputClassifier(t, dut, qos, interfaceName, oc.Input_Classifier_Type_IPV4, classifierName) + } + +} + +func checkPolicyStatistics(t *testing.T, dut *ondatra.DUTDevice, tc testCase) { + if deviations.PolicyForwardingGreEncapsulationOcUnsupported(dut) { + t.Fatalf("Dut %s %s %s does not support checking policy statistics through OC", dut.Vendor(), dut.Model(), dut.Version()) + } else { + checkPolicyStatisticsFromOC(t, dut, tc) + } +} + +func checkPolicyStatisticsFromOC(t *testing.T, dut *ondatra.DUTDevice, tc testCase) { + totalMatched := gnmi.Get(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy(trafficPolicyName).Rule(uint32(ruleSequenceMap[tc.policyRule])).MatchedPkts().State()) + previouslyMatched := ruleMatchedPackets[tc.policyRule] + if totalMatched != previouslyMatched+noOfPackets { + t.Errorf("Expected %d packets matched by policy %s rule %s for flow %s, but got %d", noOfPackets, trafficPolicyName, tc.policyRule, tc.flowName, totalMatched-previouslyMatched) + } + ruleMatchedPackets[tc.policyRule] = totalMatched +} + +func enableCapture(t *testing.T, ate *ondatra.ATEDevice, topo gosnappi.Config, otgPortNames []string) { + t.Helper() + for _, port := range otgPortNames { + t.Log("Enabling capture on ", port) + topo.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) + } + + pb, _ := topo.Marshal().ToProto() + t.Log(pb.GetCaptures()) + ate.OTG().PushConfig(t, topo) +} + +func clearCapture(t *testing.T, ate *ondatra.ATEDevice, topo gosnappi.Config) { + t.Helper() + topo.Captures().Clear() + ate.OTG().PushConfig(t, topo) +} + +func startCapture(t *testing.T, ate *ondatra.ATEDevice) gosnappi.ControlState { + t.Helper() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + ate.OTG().SetControlState(t, cs) + return cs +} + +func getCapture(t *testing.T, ate *ondatra.ATEDevice, tc testCase) string { + otg := ate.OTG() + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(tc.capturePort)) + if len(bytes) == 0 { + t.Errorf("Empty capture received for flow %s on port %s", tc.flowName, tc.capturePort) + return "" + } + f, err := os.Create(captureFilePath) + if err != nil { + t.Errorf("Could not create temporary pcap file: %v\n", err) + return "" + } + defer f.Close() + if _, err := f.Write(bytes); err != nil { + t.Errorf("Could not write bytes to pcap file: %v\n", err) + return "" + } + + return f.Name() +} + +func stopCapture(t *testing.T, ate *ondatra.ATEDevice, cs gosnappi.ControlState) { + t.Helper() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + ate.OTG().SetControlState(t, cs) + time.Sleep(5 * time.Second) +} + +func checkGreCapture(t *testing.T, tc testCase) { + if tc.captureFilename == "" { + t.Errorf("Capture file not found for flow %s", tc.flowName) + return + } + handle, err := pcap.OpenOffline(tc.captureFilename) + if err != nil { + t.Error(err) + return + } + defer handle.Close() + + var innerLayerType gopacket.LayerType + switch tc.ipType { + case ipv4: + innerLayerType = layers.LayerTypeIPv4 + case ipv6: + innerLayerType = layers.LayerTypeIPv6 + } + var packetCount uint32 = 0 + var variation uint32 = tunnelCount / 10 + tunnelPackets := make(map[string]uint32) + dscpPackets := make(map[uint8]uint32) + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + sourceDscp := (uint8)(dscpValues[int(packetCount)%len(dscpValues)]) + packetCount += 1 + ipOuterLayer, ok := ipLayer.(*layers.IPv4) + if !ok || ipOuterLayer == nil { + t.Errorf("Outer IP layer not found %d", ipLayer) + return + } + greLayer := packet.Layer(layers.LayerTypeGRE) + grePacket, ok := greLayer.(*layers.GRE) + if !ok || grePacket == nil { + t.Error("GRE layer not found") + return + } + if ipOuterLayer.Protocol != greProtocol { + t.Errorf("Packet is not encapslated properly. Encapsulated protocol is: %d", ipOuterLayer.Protocol) + return + } + innerPacket := gopacket.NewPacket(grePacket.Payload, grePacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(innerLayerType) + if ipInnerLayer == nil { + t.Error("Inner IP layer not found") + return + } + var innerPacketTOS, dscp uint8 + switch tc.ipType { + case ipv4: + ipInnerPacket, ok := ipInnerLayer.(*layers.IPv4) + if !ok || ipInnerPacket == nil { + t.Errorf("Inner layer of type %s not found", innerLayerType.String()) + return + } + innerPacketTOS = ipInnerPacket.TOS + dscp = innerPacketTOS >> 2 + case ipv6: + ipInnerPacket, ok := ipInnerLayer.(*layers.IPv6) + if !ok || ipInnerPacket == nil { + t.Errorf("Inner layer of type %s not found", innerLayerType.String()) + return + } + innerPacketTOS = ipInnerPacket.TrafficClass + dscp = innerPacketTOS + sourceDscp = sourceDscp << 2 + } + + tunnelPackets[ipOuterLayer.DstIP.String()] += 1 + if tc.checkEncapDscp { + if ipOuterLayer.TOS != innerPacketTOS { + t.Errorf("DSCP mismatch: outer DSCP %d, inner DSCP %d", ipOuterLayer.TOS, innerPacketTOS) + } + if dscp != sourceDscp { + t.Errorf("DSCP mismatch: source DSCP %d, received DSCP %d", sourceDscp, dscp) + } + dscpPackets[dscp] += 1 + } + } + if packetCount < noOfPackets { + t.Errorf("Received %d gre packets, expecting more than %d gre packets", packetCount, noOfPackets) + } + if tc.checkEncapLoadBalanced { + if len(tunnelPackets) != tunnelCount { + t.Errorf("Expected %d, tunnels, actually %d tunnels", tunnelCount, len(tunnelPackets)) + } + var aproxPacketsPerTunnel uint32 = noOfPackets / tunnelCount + for dest, count := range tunnelPackets { + t.Logf("Destination %s, count %d", dest, count) + if count < aproxPacketsPerTunnel-variation || count > aproxPacketsPerTunnel+variation { + t.Errorf("Expected aprox %d packets for tunnel %s, received %d", aproxPacketsPerTunnel, dest, count) + } + } + } + for dscp, count := range dscpPackets { + t.Logf("Packets with dscp %d: %d", dscp, count) + } +} + +func checkFlowStats(t *testing.T, ate *ondatra.ATEDevice, tc testCase) { + otg := ate.OTG() + flowMetrics := gnmi.Get(t, otg, gnmi.OTG().Flow(tc.flowName).State()) + if *flowMetrics.Counters.OutPkts != uint64(noOfPackets) { + t.Errorf("Expected %d frames to be transmitted, but got %d", noOfPackets, flowMetrics.Counters.OutPkts) + } + if *flowMetrics.Counters.InPkts != uint64(noOfPackets) { + t.Errorf("Expected %x frames to be received, but got %d", noOfPackets, flowMetrics.Counters.InPkts) + } +} diff --git a/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/metadata.textproto b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/metadata.textproto new file mode 100644 index 00000000000..c555c800384 --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/metadata.textproto @@ -0,0 +1,31 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1d7a5559-4899-42e8-a2e7-84ab7dfbcff3" +plan_id: "PF-1.2" +description: "Policy-based traffic GRE Encapsulation to IPv4 GRE tunnel" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + tunnel_state_path_unsupported: true + tunnel_config_path_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + omit_l2_mtu: true + interface_enabled: true + default_network_instance: "default" + policy_forwarding_unsupported: true + policy_forwarding_to_next_hop_oc_unsupported: true + qos_remark_oc_unsupported: true + policy_forwarding_gre_encapsulation_oc_unsupported: true + } +} +tags: TAGS_AGGREGATION diff --git a/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/README.md b/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/README.md index 83033c51633..517470d0ae9 100644 --- a/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/README.md +++ b/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/README.md @@ -1,94 +1,394 @@ -# PF-1.11 Rewrite the ingress innner packet TTL +# PF-1.11: Rewrite the ingress innner packet TTL -Create a policy-forwarding configuration using gNMI with action set-ip-ttl. +Create a policy-forwarding configuration using gNMI with action ip-ttl. + +## Summary + +This test verifies using forwarding policy to match on IPv4 and IPv6 traffic +with a particular TTL (hop-limit) value, then encapsulate the matched traffic with MPLSoGRE +IPv4 while setting the inner TTL (hop-limit) to a specified value. ## Topology -* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) ## Test setup -TODO: Complete test environment setup steps +### Test environment setup + +``` + ------- + | | + [ ATE:Port1, ATE:Port2 ] ==== LAG1 ==== | DUT | ==== LAG2 ==== [ ATE:Port3, ATE:Port4 ] + | | + ------- +``` + +* Traffic is generated from ATE:LAG1 [ATE:Port1 and ATE:Port2] +* Traffic is encapsulated with MPLSoGRE IPv4 and forwarded using ATE:LAG2 + [ATE:Port3 and ATE:Port4] +* Constants: + * `vrf_name` = "test_vrf" + * `matched_ipv4_src_net` = "10.10.50.0/24" + * `unmatched_ipv4_src_net` = "10.10.51.0/24" + * `ipv4_dst_net` = "10.10.52.0/24" + * `matched_ipv6_src_net` = "2001:f:a::0/120" + * `unmatched_ipv6_src_net` = "2001:f:b::0/120" + * `ipv6_dst_net` = "2001:f:c::0/120" + * `ipv4_tunnel_src` = "10.100.100.1" + * `ipv4_tunnel_dst_a` = "10.100.101.1" + * `ipv4_tunnel_dst_b` = "10.100.102.1" + * `tunnel_ip_ttl` = "64" + * `matched_ip_ttl` = "1" + * `unmatched_ip_ttl` = "32" + * `rewritten_ip_ttl` = "1" + * `mpls_label` = "100" + * `nexthop_group` = "NHG-1" + +### Configuration + +1. DUT:Port1 and DUT:Port2 are configured as LAG1 towards ATE:Port1 and + ATE:Port2 respectively. -inner_ipv6_dst_A = "2001:aa:bb::1/128" -inner_ipv6_dst_B = "2001:aa:bb::2/128" -inner_ipv6_default = "::/0" +2. DUT:LAG1 and ATE:LAG1 are configured with subinterfaces DUT:LAG1.10 and + ATE:LAG1.10 respectively. + * Both are configured with VLAN 10. + * DUT:LAG1.10 is configured with VRF `vrf_name`. + * DUT:LAG1.10:IPv4 is 192.168.0.1/30. + * ATE:LAG1.10:IPv4 is 192.168.0.2/30. -ipv4_inner_dst_A = "10.5.1.1/32" -ipv4_inner_dst_B = "10.5.1.2/32" -ipv4_inner_default = "0.0.0.0/0" +3. DUT:Port3 and DUT:Port4 are configured as LAG2 towards ATE:Port3 and + ATE:Port4 respectively. + * DUT:LAG2:IPv4 is 192.168.1.1/30. + * ATE:LAG2:IPv4 is 192.168.1.2/30. -outer_ipv6_src = "2001:f:a:1::0" -outer_ipv6_dst_A = "2001:f:c:e::1" -outer_ipv6_dst_B = "2001:f:c:e::2" -outer_ipv6_dst_def = "2001:1:1:1::0" -outer_dst_udp_port = "6635" -outer_dscp = "26" -outer_ip-ttl = "64" +4. DUT is configured with default route with nexthop as ATE:LAG2:IPv4. This + is to ensure the tunnel destination of the MPLSoGRE tunnel is resolvable. + +5. DUT is configured with nexthop-group named `nexthop_group`. The nexthops of + this nexthop-group are configured as MPLSoGRE IPv4 tunnels: + * Outer TTL value: `tunnel_ip_ttl` + * Tunnel source: `ipv4_tunnel_src` + * Tunnel destination: `ipv4_tunnel_dst_a` or `ipv4_tunnel_dst_b` + * MPLS label: `mpls_label` + +6. DUT is configured with a policy-forwarding in `vrf_name` with this rule: + * Match condition: + * Matching on IPv4 traffic with TTL "matching_ip_ttl" + * Actions: + * Forward to nexthop-group `nexthop_group` + * Inner TTL rewritten as `rewritten_ip_ttl` + +7. Repeat step 6 but for IPv6 traffic. + +8. DUT is configured with default routes (one for IPv4 and one for IPv6) in + `vrf_name` with nexthop as nexthop-group `nexthop_group`. ## Procedure -### TE-1.11.1 Rewrite the ingress innner packet TTL = 1, if the incoming TTL = 1. -**[TODO]** Test code needs to be implemented. +**[TODO]** Implement test code. + +### TE-1.11.1 Rewrite the ingress innner packet TTL = 1, if the incoming TTL = 1 +for IPv4 traffic. + +ATE action: +* Generate total 1000 **IPv4 packets** from ATE:Port1 and ATE:Port2 + with: + * Source IP from random addresses in `matched_ipv4_src_net` to destination + IP addresses in `ipv4_dst_net` addresses. + * Use 512 bytes frame size. + * Set TTL of all packets to `matched_ip_ttl`. + +* Generate total 1000 **IPv4 packets** from ATE:Port1 and ATE:Port2 + with: + * Source IP from random addresses in `unmatched_ipv4_src_net` to + destination IP addresses in `ipv4_dst_net` addresses. + * Use 512 bytes frame size. + * Set TTL of all packets to `unmatched_ip_ttl`. + +Verify: +* The total packet count of traffic sent from ATE:Port1 and ATE:Port2 should + be equal to the sum of all packets received on ATE:Port3 and ATE:Port4. +* All packets received on ATE:Port3 and ATE:Port4 are encapsulated with + MPLSoGRE IPv4 with: + * MPLS label: `mpls_label` + * Source IPv4 address: `ipv4_tunnel_src` + * Destination IPv4 address: `ipv4_tunnel_dst_a` or `ipv4_tunnel_dst_b` + * Tunnel IP TTL: `tunnel_ip_ttl` +* Packets with source IPv4 addresses from subnet `matched_ipv4_src_net` has + inner IP TTL set to `rewritten_ip_ttl`. +* Packets with source IPv4 addresses from subnet `unmatched_ipv4_src_net` has + inner IP TTL set to `unmatched_ip_ttl` - 1. -Canonical OpenConfig for policy forwarding, matching IP prefix and TTL = 1 with action -set inner packet TTL = 1. +### TE-1.11.2 Rewrite the ingress innner packet TTL = 1, if the incoming TTL = 1 +for IPv6 traffic. + +ATE action: +* Generate total 1000 **IPv6 packets** from ATE:Port1 and ATE:Port2 + with: + * Source IP from random addresses in `matched_ipv6_src_net` to destination + IP addresses in `ipv6_dst_net` addresses. + * Use 512 bytes frame size. + * Set TTL of all packets to `matched_ip_ttl`. + +* Generate total 1000 **IPv6 packets** from ATE:Port1 and ATE:Port2 + with: + * Source IP from random addresses in `unmatched_ipv6_src_net` to + destination IP addresses in `ipv6_dst_net` addresses. + * Use 512 bytes frame size. + * Set TTL of all packets to `unmatched_ip_ttl`. + +Verify: +* The total packet count of traffic sent from ATE:Port1 and ATE:Port2 should + be equal to the sum of all packets received on ATE:Port3 and ATE:Port4. +* All packets received on ATE:Port3 and ATE:Port4 are encapsulated with + MPLSoGRE IPv4 with: + * MPLS label: `mpls_label` + * Source IPv4 address: `ipv4_tunnel_src` + * Destination IPv4 address: `ipv4_tunnel_dst_a` or `ipv4_tunnel_dst_b` + * Tunnel IP TTL: `tunnel_ip_ttl` +* Packets with source IPv6 addresses from subnet `matched_ipv6_src_net` has + inner IP TTL set to `rewritten_ip_ttl`. +* Packets with source IPv6 addresses from subnet `unmatched_ipv6_src_net` has + inner IP TTL set to `unmatched_ip_ttl` - 1. + +## Canonical OC +**[TODO]**: Add MATCH_ACTION policy-forwarding type to OpenConfig Public data + models. + +**[TODO]**: Add ip-ttl policy-forwarding action to OpenConfig Public data + models, pending https://github.com/openconfig/public/pull/1313. ```json { - "openconfig-network-instance": { - "network-instances": [ + "network-instances": { + "network-instance": [ { - "afts": { - "policy-forwarding": { - "policies": [ + "name": "test_vrf", + "config": { + "name": "test_vrf" + }, + "policy-forwarding": { + "policies": { + "policy": [ { + "policy-id": "retain ttl", "config": { "policy-id": "retain ttl", - "type": "PBR_POLICY" + "type": "MATCH_ACTION" }, - "policy": "retain ttl", - "rules": [ - { - "config": { + "rules": { + "rule": [ + { "sequence-id": 1, + "config": { + "sequence-id": 1 + }, + "ipv4": { + "config": { + "hop-limit": 1 + } + }, + "action": { + "config": { + "next-hop-group": "NHG-1", + "ip-ttl": 1 + } + } }, - "ipv6": { + { + "sequence-id": 2, + "config": { + "sequence-id": 2 + }, + "ipv6": { + "config": { + "hop-limit": 1 + } + }, + "action": { + "config": { + "next-hop-group": "NHG-1", + "ip-ttl": 1 + } + } + } + ] + } + } + ] + } + }, + "static": { + "next-hop-groups": { + "next-hop-group": [ + { + "name": "NHG-1", + "config": { + "name": "NHG-1" + }, + "next-hops": { + "next-hop": [ + { + "index": 1, "config": { - "destination-address": "router_ip" - "hop-limit": 1 + "index": 1 } }, - "action": { - "set-ip-ttl": 1 #TODO: Add set-ip-ttl [https://github.com/openconfig/public/pull/1263/files] - } + { + "index": 2, + "config": { + "index": 2 + } + } + ] + } + } + ] + }, + "next-hops": { + "next-hop": [ + { + "index": 1, + "config": { + "index": 1 + }, + "encap-headers": { + "encap-header": [ + { + "index": 1, + "config": { + "index": 1, + "type": "MPLS" + }, + "mpls": { + "config": { + "label": 100 + } + } + }, + { + "index": 2, + "config": { + "index": 2, + "type": "GRE" + }, + "gre": { + "config": { + "src-ip": "10.100.100.1", + "dst-ip": "10.100.101.1", + "ttl": 64 + } + } + } + ] + } + }, + { + "index": 2, + "config": { + "index": 2 + }, + "encap-headers": { + "encap-header": [ + { + "index": 1, + "config": { + "index": 1, + "type": "MPLS" + }, + "mpls": { + "config": { + "label": 100 + } + } + }, + { + "index": 2, + "config": { + "index": 2, + "type": "GRE" + }, + "gre": { + "config": { + "src-ip": "10.100.100.1", + "dst-ip": "10.100.102.1", + "ttl": 64 + } + } + } + ] + } + } + ] + } + }, + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "STATIC", + "config": { + "identifier": "STATIC", + "name": "STATIC" + }, + "static-routes": { + "static": [ + { + "prefix": "0.0.0.0/0", + "config": { + "prefix": "0.0.0.0/0" + }, + "next-hop-group": { + "name": "NHG-1" + } + }, + { + "prefix": "::/0", + "config": { + "prefix": "::/0" + }, + "next-hop-group": { + "name": "NHG-1" + } } ] } - ] - } + } + ] } } ] } } ``` -* Push the gNMI the policy forwarding configuration -* Push the configuration to DUT using gnmi.Set with REPLACE option -* Send traffic from ATE port 1 to DUT port 1 with inner packet TTL as 1. -* Using OTG, validate ATE port 2 receives MPLS-IN-GRE packets - * Validate destination IPs are outer_ipv6_dst_A and outer_ipv6_dst_B - * Validate MPLS label is set - * Validate inner packet ttl as 1. - * Validate outer packet ttl to be "outer_ip-ttl" ## OpenConfig Path and RPC Coverage ```yaml paths: - /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/destination-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/hop-limit: /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/hop-limit: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/next-hop-group: + # /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/ip-ttl: # See TODO + + /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name: + /network-instances/network-instance/static/next-hop-groups/next-hop-group/next-hops/next-hop/config/index: + /network-instances/network-instance/static/next-hops/next-hop/config/index: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/config/index: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/config/type: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/mpls/config/label: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/gre/config/dst-ip: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/gre/config/src-ip: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/gre/config/ttl: + + /network-instances/network-instance/protocols/protocol/identifier: + /network-instances/network-instance/protocols/protocol/config/identifier: + /network-instances/network-instance/protocols/protocol/static-routes/static/prefix: + /network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hop-group/name: + rpcs: gnmi: @@ -102,4 +402,4 @@ rpcs: ## Required DUT platform -* FFF \ No newline at end of file +* FFF diff --git a/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/metadata.textproto b/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/metadata.textproto new file mode 100644 index 00000000000..0b32855f2ea --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/inner_ttl_ipv4/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "171f2571-d775-4f9b-b9bf-3584e52f78a6" +plan_id: "PF-1.11" +description: "Rewrite the ingress innner packet TTL" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/README.md b/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/README.md new file mode 100644 index 00000000000..8da28b4e559 --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/README.md @@ -0,0 +1,787 @@ +# RT-3.52: Multidimensional test for Static GUE Encap/Decap based on BGP path selection and selective DSCP marking + +## Summary + +The goal of this test is to: + +1. Test the implementation of Static GUE encap where the Tunnel endpoint is + resolved over EBGP, while the Payload's destination is learned over IBGP. + +2. Confirm that before GUE encapsulation, the device correctly selects the path + for the payload destination from multiple available IBGP routes. Path + selection needs to follow the BGP best path algorithm rules, like giving + preference to routes with a higher BGP Local Preference. Furthermore, it + must successfully switch over to backup/alternative IBGP paths when the + preferred path fails. + +3. Validate that encapsulated traffic has its TOS bits copied from the inner + header to the outer header. + +4. The DUT will be configured to encapsulate traffic over multiple tunnels. It + is expected to tunnel traffic towards the correct tunnel destination, based + on the IBGP learned routes. + +5. The DUT will be configured to decapsulate traffic received over various + tunnel destinations, and it is expected to accurately decapsulate traffic + across all these received destinations. + +6. Confirm that the TTL value for the outer IP header created during GUE + encapsulation can be explicitly configured. + +7. Confirm that when the DUT handles GUEv1 traffic from the reverse path, it + successfully performs decapsulation. During this decapsulation process, the + system must not transfer the DSCP and TTL bits from the outer header to the + inner header. Instead, following decapsulation, the DUT should decrement the + inner header's TTL by 1 before forwarding the packet + +## Topology + +Please note: In the diagram below, ATE1 and ATE2 can be the same or different +ATE + +```mermaid +graph LR; +subgraph DUT [DUT] + B1[Port1] + B2[Port2] + B4[Port4] +end + +subgraph ATE2 [ATE2] + C1[Port1] + C3[Port3] +end + +A1[ATE1:Port1] <-- IBGP(ASN100) --> B1; +B2 <-- IBGP(ASN100) --> C1; +B4 <-- EBGP(ASN100:ASN200) --> C3; +``` + +``` ++-------------------------------------------+ +| ATE1 (ASN-100) | +|-------------------------------------------| +| IBGP Peer: | +| - $ATE1_IBGP.v4/32 | +| - $ATE1_IBGP.v6/128 | +| | +| Prefixes Advertised: | +| - $ATE1_PORT1_user[1-5].v4/24 | +| - $ATE1_PORT1_user[1-5].v6/64 | +| | +| ATE1_Port1 <-> DUT_Port1: | +| - $ATE1PORT1_DUTPORT1_1.v4/31 | +| - $ATE1PORT1_DUTPORT1_1.v6/127 | ++---------------------+---------------------+ + | + | (IS-IS and IBGP) + | ++---------------------+---------------------+ +| Port1: (to ATE1) | +| - $ATE1PORT1_DUTPORT1_2.v4/31 | +| - $ATE1PORT1_DUTPORT1_2.v6/127 | +|-------------------------------------------| +| DUT (ASN-100) | +|-------------------------------------------| +| Loopback: | +| - $DUT_lo0.v4 | +| - $DUT_lo0.v6 | +|-------------------------------------------| +| Port2: (to ATE2_Port1) <------------+ (IS-IS and IBGP) +| - $ATE2PORT1_DUTPORT2_2.v4/31 | | +| - $ATE2PORT1_DUTPORT2_2.v6/127 | | +|-------------------------------------------| | +| Port4: (to ATE2_Port3) | | +| - $ATE2PORT3_DUTPORT4_2.v4/31 <----------------------+ (EBGP) +| - $ATE2PORT3_DUTPORT4_2.v6/127 | | | ++-------------------------------------------+ | | + | | ++-----------------------------------------------------+ | | +| ATE2 (ASN 100 & 200) | | | +|-----------------------------------------------------+ | | +| Port1: (to DUT) <--+ | +| - $ATE2PORT1_DUTPORT2_1.v4/31 | | +| - $ATE2PORT1_DUTPORT2_1.v6/127 | | +|-----------------------------------------------------+ | +| Port3: (to DUT) <-------------+ +| - `$ATE2PORT3_DUTPORT4_1.v4/31` | +| - `$ATE2PORT3_DUTPORT4_1.v6/127` | +|-----------------------------------------------------+ +| IBGP Peer IPs: | +| - $ATE2_C.IBGP.v6/128 | +| - $ATE2_M.IBGP.v4/32, .v6/128 | +| - $ATE2_IBGP.v4/32, .v6/128 | +| Prefixes Advertised over IBGP and EBGP peering: | +| - $ATE2_INTERNAL[6-10].v4/24 | +| - $ATE2_INTERNAL[6-10].v6/64 | ++-----------------------------------------------------+ +``` + +### All IP addresses for the test: + +**Different IP addresses** | **Description** +:----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------- +
`$ATE1PORT1_DUTPORT1_1.v4/31`
`$ATE1PORT1_DUTPORT1_1.v6/127` | Configured on the ATE1PORT1 port of ATE1_Port1<>DUT_Port1 point-to-point connection and used for IS-IS adjacency. +
`$ATE1PORT1_DUTPORT1_2.v4/31`
`$ATE1PORT1_DUTPORT1_2.v6/127` | Configured on the DUTPORT1 port of ATE1_Port1<>DUT_Port1 point-to-point connection and used for IS-IS adjacency. +
`$ATE1_IBGP.v4/32`
`$ATE1_IBGP.v6/128` | Exchanged over IS-IS adjacency. Used to establish IBGP peering between ATE1 and DUT1 +
`$ATE1_PORT1_user1.v4/24`
`$ATE1_PORT1_user2.v4/24`
`$ATE1_PORT1_user3.v4/24`
`$ATE1_PORT1_user4.v4/24`
`$ATE1_PORT1_user5.v4/24`
`$ATE1_PORT1_user1.v6/64`
`$ATE1_PORT1_user2.v6/64`
`$ATE1_PORT1_user3.v6/64`
`$ATE1_PORT1_user4.v6/64`
`$ATE1_PORT1_user5.v6/64` |
- `$ATE1_IBGP.v[46]` advertises these user prefixes to `DUT_lo0.v[46]` over IBGP
- `$DUT_lo0.v[46]` advertises these further to `$ATE2_PORT1_IBGP.v[46]` and to `$ $ATE2_C.IBGP.v6` +
`$ATE2PORT1_DUTPORT2_1.v4/31`
`$ATE2PORT1_DUTPORT2_1.v6/127` |
Configured on the ATE2Port1 port of ATE2_Port1<>DUT_Port2 point-to-point connection and used for IS-IS adjacency. +
`$ATE2PORT1_DUTPORT2_2.v4/31`
`$ATE2PORT1_DUTPORT2_2.v6/127` | Configured on DUTPORT2 of the DUT_PORT2<>ATE2_PORT1 point-to-point connection and used for IS-IS adjacency
+
`$ATE2_PORT1.IBGP.v4/32`
`$ATE2_PORT1.IBGP.v6/128` | Regular IBGP peering between `$ATE2_PORT1.IBGP.v[46]` and DUT_lo0.v[46] +
`$ATE2_C.IBGP.v6/128`
`$ATE2_PPNH[12].v6/128` | - For IBGP peering between `$ATE2_C.IBGP.v6` and `DUT_lo0.v6`
- Pseudo protocol next-hop for the IBGP routes advertised by `$ATE2_C.IBGP.v6` to the DUT +
`$ATE2_M.IBGP.v4/32`
`$ATE2_M.IBGP.v6/128` | For IBGP peering between `$ATE2_M.IBGP.v[4\|6]` and `DUT_lo0.v[4\|6]` +
`$ATE2PORT3_DUTPORT4_1.v4/31`
`$ATE2PORT3_DUTPORT4_1.v6/127` | Configured on ATE2Port3 port of the ATE2_Port3<>DUT_Port4 point-to-point peering and used for EBGP peering between DUT_PORT4<>ATE2_PORT3 +
`$ATE2PORT3_DUTPORT4_2.v4/31`
`$ATE2PORT3_DUTPORT4_2.v6/127` | Configured on DUTPort4 port of the ATE2_Port3<>DUT_Port4 point-to-point peering and used for EBGP peering between DUT_PORT4<>ATE2_PORT3 + +
`$DUT_lo0.v4`
`$DUT_lo0.v6` | Adertised over IS-IS and used for IBGP peering. Also used as IPoUDP tunnel source address +`$DUT_TE11.v4/32` | IPoUDP tunnel destination address on the DUT. This IP MUST receive traffic meant for single shard on the DUT +`$DUT_TE10.v4/32` | IPoUDP tunnel destination address on the DUT. This IP MUST receive traffic meant for multiple shards on the DUT +`$ATE2_INTERNAL_TE11.v4/32` | IPoUDP tunnel destination address on the ATE2. This IP MUST receive traffic meant for single shard on the ATE +`$ATE2_INTERNAL_TE10.v4/32` | IPoUDP tunnel destination address on the ATE. This IP MUST receive traffic meant for mulitple shards on the ATE +
`$ATE2_INTERNAL6.v4/24`
`$ATE2_INTERNAL6.v6/64`
`$ATE2_INTERNAL7.v4/24`
`$ATE2_INTERNAL7.v6/64`
`$ATE2_INTERNAL8.v4/24`
`$ATE2_INTERNAL8.v6/64`
`$ATE2_INTERNAL9.v4/24`
`$ATE2_INTERNAL9.v6/64`
`$ATE2_INTERNAL10.v4/24`
`$ATE2_INTERNAL10.v6/64` |
- Internal Public prefixes
- Advertised to the DUT over the IBGP peering between `ATE2_PORT1.IBGP.v[46]`<>`$DUT_lo0.v[46]` and `$ATE2_C.IBGP.v6`<>`$DUT_lo0.v6`
- Advertised further to `$ATE1_IBGP.v[46]` over the IBGP peering between `$ATE1_IBGP.v[46]`<>`DUT_lo0.v[46]` over their respective AFI peering + +### Advertisements: + +**IS-IS:** + +| **Different IS-IS L2 adjacencies** | **Prefixes advertised** | +| :--------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `ATE1_Port1<>DUT_Port1` |
- `ATE1_PORT1` --> `DUT_PORT1`: `$ATE1_IBGP.v4`, `$ATE1_IBGP.v6`
- `DUT_PORT1` --> `ATE1_PORT1`: `$DUT_lo0.v4`, `$DUT_lo0.v6` | +| `ATE2_Port1<>DUT_Port2` |
- `ATE2_Port1` --> `DUT_Port2`: `$ATE2_PORT1.IBGP.v4/32`, `$ATE2_PORT1.IBGP.v6/128`, `$ATE2_C.IBGP.v6/128`, `$ATE2_M.IBGP.v6/128`
- `DUT_Port2` --> `ATE2_Port1`: `$DUT_lo0.v4`, `$DUT_lo0.v6` | + +**BGP:** + +**Different peering** | **BGP peering type** | **Prefixes advertised** +:------------------------------------------- | :------------------- | :---------------------- +`$ATE1_IBGP.v[46]<>$DUT_lo0.v[46]` | IBGP |
- `$DUT_lo0.v[46]` is the route-reflector server and `$ATE1_IBGP.v[46]` is the route-reflector client
- `$ATE1_IBGP.v[46]` advertises prefixes `$ATE1_PORT1_user[1-5].v[46]` to `$DUT_lo0.v[46]` on their respective AFI peering
- `$DUT_lo0.v[46]` advertises `$ATE2_INTERNAL[6-10].v[46]` to `$ATE1_IBGP.v[46]` on their respective AFI peering
- MULTIPATH enabled on this peering +`$ATE2_IBGP.v[46]<>$DUT_lo0.v[46]` | IBGP |
- `$ATE2_IBGP.v[46]` advertises `$ATE2_INTERNAL[6-10].v[46]`, `$ATE2_INTERNAL_TE10.v4/30`. and `$ATE2_INTERNAL_TE11.v4/30` to `DUT_lo0.v[46]` on their respective AFI peering
- `$DUT_lo0.v[46]` advertises `$ATE1_PORT1_user[1-5].v[46]`, `$DUT_TE10.v4/30`, `$DUT_TE11.v4/30`, `$DUT_TE10.v4/32` and `$DUT_TE11.v4/32` to `$ATE2_IBGP.v[46]` +`$ATE2_C.IBGP.v6<>$DUT_lo0.v6` | IBGP |
- `$ATE2_C.IBGP.v6` advertises `$ATE2_INTERNAL[6-8].v[46]` to `DUT_lo0.v6` with Next-hop as $ATE2_PPNH1.v6/128 and a Local-Pref of 200. Similarly advertises `$ATE2_INTERNAL[9-10].v[46]` to `DUT_lo0.v6` with Next-hop as $ATE2_PPNH2.v6/128 and a Local-Pref of 200. **Please Note:** These prefixes are gradually advertised by `$ATE2_C.IBGP.v6` in different Sub tests.
`$ATE2_INTERNAL6.v[46]` in RT-3.52.2 to RT-3.52.9
`$ATE2_INTERNAL7.v[46]` in RT-3.52.3 to RT-3.52.9
`$ATE2_INTERNAL8.v[46]` in RT-3.52.4 to RT-3.52.9
`$ATE2_INTERNAL9.v[46]` in RT-3.52.5 to RT-3.52.9
`$ATE2_INTERNAL10.v[46]` in RT-3.52.6 to RT-3.52.9
- `$DUT_lo0.v6` advertises `$ATE1_PORT1_user[1-5].v[46]` to `$ATE2_C.IBGP.v6` +`$ATE2_M.IBGP.v[46]<>$DUT_lo0.v[46]` | IBGP |
- `DUT_lo0.v[46]` advertises, all its ECMP routes to `$ATE2_M.IBGP.v[46]` on the respective AFI peering.
- This peering will have ADD-Path for Multipath routes enabled. +`$ATE2_Port3<>$DUT_Port4` | EBGP |
- `ATE2_Port3` advertises `$ATE2_INTERNAL_TE10.v4/30` and `$ATE2_INTERNAL_TE11.v4/30` to `DUT_Port4`
- `DUT_Port4` advertises `$DUT_TE10.v4/32`, `$DUT_TE11.v4/32`, `$DUT_TE10.v4/30` and `$DUT_TE11.v4/30` to `ATE2_Port3` +Local ASN as ASN100 for following interfaces | | `$ATE1_IBGP.v[46]`, `$DUT_lo0.v[46]`, `$ATE2_IBGP.v[46]`, `$DUT_lo0.v[46]`, `$ATE2_C.IBGP.v6`, `$ATE2_M.IBGP.v[46]`, `$DUT_lo0.v[46]`, `$DUT_Port2` +Local ASN as ASN200 for following interfaces | | `$ATE2_Port3` + +**Different Flows used throughout the test:** + +| Src_destination of flows | From_IP --> To_IP | DSCP | Tunnel endpoint used | +| :------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | :--- | :-------------------------- | +| Flow-Set#1 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user1.v4/24 --> $ATE2_INTERNAL6.v4/24` | BE1 | `ATE2_INTERNAL_TE11.v4/32` | +| Flow-Set#1 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user1.v6/64 --> $ATE2_INTERNAL6.v6/64` | BE1 | `ATE2_INTERNAL_TE11.v4/32` | +| Flow-Set#1 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user2.v4/24 --> $ATE2_INTERNAL7.v4/24` | AF1 | `ATE2_INTERNAL_TE11.v4/32` | +| Flow-Set#1 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user2.v6/64 --> $ATE2_INTERNAL7.v6/64` | AF1 | `ATE2_INTERNAL_TE11.v4/32` | +| Flow-Set#1 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user3.v4/24 --> $ATE2_INTERNAL8.v4/24` | AF2 | `ATE2_INTERNAL_TE11.v4/32` | +| Flow-Set#1 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user3.v6/64 --> $ATE2_INTERNAL8.v6/64` | AF2 | `ATE2_INTERNAL_TE11.v4/32` | +| Flow-Set#2 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user4.v4/24 --> $ATE2_INTERNAL9.v4/24` | AF3 | `$ATE2_INTERNAL_TE10.v4/32` | +| Flow-Set#2 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user4.v6/64 --> $ATE2_INTERNAL9.v6/64` | AF3 | `$ATE2_INTERNAL_TE10.v4/32` | +| Flow-Set#2 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user5.v4/24 --> $ATE2_INTERNAL10.v4/24` | AF4 | `$ATE2_INTERNAL_TE10.v4/32` | +| Flow-Set#2 from ATE1_Port1 --> ATE2_[Either Port1 or Port3 depending on the FIB entries of the DUT] | `$ATE1_PORT1_user5.v6/64 --> $ATE2_INTERNAL10.v6/64` | AF4 | `$ATE2_INTERNAL_TE10.v4/32` | +| Flow-Set#3 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE11.v4/32 | `$ATE2_INTERNAL6.v4/24 --> $ATE1_PORT1_user1.v4/24` | BE1 | `$DUT_TE11.v4/30` | +| Flow-Set#3 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE11.v4/32 | `$ATE2_INTERNAL6.v6/64 --> $ATE1_PORT1_user1.v6/64` | BE1 | `$DUT_TE11.v4/30` | +| Flow-Set#3 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE11.v4/32 | `$ATE2_INTERNAL7.v4/24 --> $ATE1_PORT1_user2.v4/24` | AF1 | `$DUT_TE11.v4/30` | +| Flow-Set#3 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE11.v4/32 | `$ATE2_INTERNAL7.v6/64 --> $ATE1_PORT1_user2.v6/64` | AF1 | `$DUT_TE11.v4/30` | +| Flow-Set#3 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE11.v4/32 | `$ATE2_INTERNAL8.v4/24 --> $ATE1_PORT1_user3.v4/24` | AF2 | `$DUT_TE11.v4/30` | +| Flow-Set#3 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE11.v4/32 | `$ATE2_INTERNAL8.v6/64 --> $ATE1_PORT1_user3.v6/64` | AF2 | `$DUT_TE11.v4/30` | +| Flow-Set#4 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE10.v4/32 | `$ATE2_INTERNAL9.v4/24 --> $ATE1_PORT1_user4.v4/24` | AF3 | `$DUT_TE10.v4/30` | +| Flow-Set#4 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE10.v4/32 | `$ATE2_INTERNAL9.v6/64 --> $ATE1_PORT1_user4.v6/64` | AF3 | `$DUT_TE10.v4/30` | +| Flow-Set#4 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE10.v4/32 | `$ATE2_INTERNAL10.v4/24 --> $ATE1_PORT1_user5.v4/24` | AF4 | `$DUT_TE10.v4/30` | +| Flow-Set#4 from ATE2_Port3 --> ATE1_Port1 are GUE encaped with Tunnel destination as $DUT_TE10.v4/32 | `$ATE2_INTERNAL10.v6/64 --> $ATE1_PORT1_user5.v6/64` | AF4 | `$DUT_TE10.v4/30` | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL6.v4/24 --> $ATE1_PORT1_user1.v4/24` | BE1 | N/A | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL6.v6/64 --> $ATE1_PORT1_user1.v6/64` | BE1 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL7.v4/24 --> $ATE1_PORT1_user2.v4/24` | AF1 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL7.v6/64 --> $ATE1_PORT1_user2.v6/64` | AF1 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL8.v4/24 --> $ATE1_PORT1_user3.v4/24` | AF2 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL8.v6/64 --> $ATE1_PORT1_user3.v6/64` | AF2 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL9.v4/24 --> $ATE1_PORT1_user4.v4/24` | AF3 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL9.v6/64 --> $ATE1_PORT1_user4.v6/64` | AF3 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL10.v4/24 --> $ATE1_PORT1_user5.v4/24` | AF4 | | +| Flow-Set#5 from ATE2:Port1 to ATE1:Port1 are sent Unencaped | `$ATE2_INTERNAL10.v6/64 --> $ATE1_PORT1_user5.v6/64` | AF4 | | + + +### DUT Configuration: + +In addition to the adjacencies and peering configurations described in the +tables above, the DUT requires the following configurations: + +* **IS-IS:** + + * The DUT's loopback interface must be passive for IS-IS. + +* **BGP:** + + * Define import and export route policies to match the advertisements for + each BGP peering. + +* **Static GUE Encapsulation:** + + * Configure static GUE encapsulation as follows: + + * Define UDP ports to be used for IPv4oUDP and IPv6oUDP which is 6080. + * Define the Tunnel NHG configuration with these parameters: + + * `ttl = 128` + * `tunnel-source = $DUT_lo0.v4` + * `tunnel-destination1 = $ATE2_INTERNAL_TE11.v4/32`. + * `tunnel-destination2 = $ATE2_INTERNAL_TE10.v4/32`. + * The DUT must have a static route pointing `"$ATE2_PPNH1.v6/128"` + and `$ATE2_PPNH2.v6/128` to the NHG created above (below is an + example of the static route). The IBGP peer $ATE2_C.IBGP.v6/128 + is expected to advertise both IPv4 and IPv6 prefixes with the + Next-Hop as `"$ATE2_PPNH1.v6/128"` or `"$ATE2_PPNH2.v6/128"` + `static dst: ATE2_PPNH1.v6/128 next-hop: + $ATE2_INTERNAL_TE11.v4/32 static dst: ATE2_PPNH2.v6/128 + next-hop: $ATE2_INTERNAL_TE10.v4/32` + +* **GUE Decapsulation:** + + * For a GUE decapsulation node, configure the following: + + * UDP port 6080 (configurable) must be used for decapsulating IPv4 and + IPv6 payload. The implementation MUST look at the first 4 bits of + the UDP payload to determine the GUE version as well as the IP + version of the payload to be either IPv4 or IPv6 as explained in the + [IETF draft](https://datatracker.ietf.org/doc/html/draft-ietf-intarea-gue-09#section-4) + * The Decapsulation node must be configured for decapsulating traffic + received for the range i.e $DUT_TE11.v4/30 and $DUT_TE10.v4/30 in + place of the corresponding /32 addresses. + * After decapsulation, the outer TTL and DSCP bits must not be copied + to the inner header + +### Following Health checks to be run Pre and Post every sub test + +Use # Health-1.1: Generic Health Check. If errors identified then the test Must +fail. + +## RT-3.52.1: Baseline + +* **Test Steps:** + + * Generate DUT Configuration as specified in the test setup + * Use gnmi to push the configuration to the DUT + * Ensure no prefixes are exchanged over the IBGP peering between + `$ATE2_C.IBGP.v6` and `$DUT_lo0.v6`. Validate this using OC +``` +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received +/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent +``` + * Validate DUT using health-1.1 steps + * Start flow set 1, 2 and 5 + * Set 50,000 packets per flow at 1,000pps + * Validate 0 packet loss on the ATE side + * Execute post-test health checks and compare the results with the + baseline. Verify that there are no core dumps, or other issues. + +## RT-3.52.2: BE1 Traffic Migrated from being routed over the DUT_Port2 --> + +ATE2_Port1 path to DUT_Port4 --> ATE2_Port3 path + +* **Test Steps:** + + * Re-run he baseline test (RT-3.52.1), with flows active in Flow-Set #1, + Flow-Set #2, and Flow-Set #5 + * Execute the health checks described previously + * The IBGP session between `$ATE2_C.IBGP.v6`and `$DUT_lo0.v6` should now + advertise only `$ATE2_INTERNAL6.v[46]` with a local preference of 200 + and Pseudo Protocol Next-Hop as `$ATE2_PPNH1.v6/128` + * `$DUT_lo0.v6` advertises `$ATE1_PORT1_user[1-5].v[46]` to + `$ATE2_C.IBGP.v6` + +* **Expectations:** + + * Routes to prefixes `$ATE2_INTERNAL6.v4/24` and `$ATE2_INTERNAL6.v6/64`, + learned from `$ATE2_C.IBGP.v6/128`, should be placed in the FIB. Other + prefixes from ATE2 will continue to be learnt via the IBGP peering + between `$ATE2_PORT1.IBGP.v[46]` and `$DUT_lo0.v[46]` and hence will be + in the DUT's FIB. Please use the AFT paths specified below for + verification. + * Flows destined for `$ATE2_INTERNAL6.v4/24` and `$ATE2_INTERNAL6.v6/64` + should be GUE-encapsulated with tunnel destination + `$ATE2_INTERNAL_TE11.v4` and routed over the EBGP peering between + `$ATE2_Port3` and `$DUT_Port4`, and these flows must be 100% successful + (zero loss). Please check this on the ATE2 (capture 1 packet using OTG + from each flow and then decode the packet to validate headers. Remaining + packets can be trusted to also be encapsulated.). Verify that traffic is + unencapsulated before the migration and encapsulated after the migration. + * The outer header TTL should be 127 upon arrival at `ATE2_Port1` (before + decapsulation). Please check this on the ATE2. + * The outer header DSCP bits should be the same as the inner header DSCP + bits when received at `ATE2_Port1` (before decapsulation). Please verify + this on the ATE2. + * Verify on ATEs that the amount of packets sent is the same as the amount + of encaped packets received per tunnel endpoint. Also check the + interface counters using OC. + `/interfaces/interface/state/counters/out-unicast-pkts` + * Unencapsulated flows from ATE2 to `ATE1_Port1` must have 100% success + (Zero loss), routing via the IBGP peering between `$ATE2_IBGP.v[46]` and + `$DUT_lo0.v[46]` + * Post-test health checks should be performed and compared against the + baseline. Verify the absence of drops or core dumps. If any, the test + Must fail + +``` +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix +``` + +## RT-3.52.3 to RT-3.52.6: AF1-AF4 Traffic Migrated to DUT --> ATE2_Port3 + +Follow the same steps as in RT-3.52.2 and gradually move one Traffic class at a +time in the following order. Note changes in RT-3.52.5 and RT-3.52.6 + +### RT-3.52.3: Migrate AF1 Flows + +``` +* Migrate routing of AF1 flows from `DUT_Port2` --> `ATE2_Port1` to +`DUT_Port4` --> `ATE2_Port3`. +* BE1 and AF1 are now migrated +* Complete validation same as RT-3.52.2 above +``` + +### RT-3.52.4: Migrate AF2 Flows + +``` +* Migrate routing of AF2 flows from `DUT_Port2` --> `ATE2_Port1` to +`DUT_Port4` --> `ATE2_Port3`. +* BE1-AF2 are now migrated +* Complete validation same as RT-3.52.2 above +``` + +### RT-3.52.5: Migrate AF3 Flows + +``` +* Migrate routing of AF3 flows from `DUT_Port2` --> `ATE2_Port1` to +`DUT_Port4` --> `ATE2_Port3`. + +* `$ATE2_C.IBGP.v6` will advertise `$ATE2_INTERNAL9.v4/24` and + `$ATE2_INTERNAL9.v6/64` with next-hop as `$ATE2_PPNH2.v6/128`. + Traffic towards `$ATE2_INTERNAL9.v[46]/24` will have tunnel + destination `$ATE2_INTERNAL_TE10.v4/32`. +* BE1-AF3 are now migrated +* Complete validation same as RT-3.52.2 above +``` + +### RT-3.52.6: Migrate AF4 Flows: + +``` +* Migrate routing of AF4 flows from `DUT_Port2` --> `ATE2_Port1` to +`DUT_Port4` --> `ATE2_Port3`. + +* `$ATE2_C.IBGP.v6` will advertise `$ATE2_INTERNAL10.v4/24` and + `$ATE2_INTERNAL10.v6/64` with next-hop as `$ATE2_PPNH2.v6/128` + Traffic towards `$ATE2_INTERNAL10.v[46]` will have tunnel + destination `$ATE2_INTERNAL_TE10.v4/32`. +* BE1-AF4 are now migrated +* Complete validation same as RT-3.52.2 above +``` + +### RT-3.52.7: DUT as a GUE Decap Node + +* **Situation:** + + * The test begins from the state established in RT-3.52.3, where all + traffic from ATE1 to ATE2 is encapsulated by the DUT and routed via + DUT_Port4 --> ATE2_Port3 path. + +* **Test Steps:** + + * Perform all previously defined health checks as a baseline. + * Stop Flow-Set #5 and start Flow-Set #4, resulting in active flows for + Flow-Set #1 through Flow-Set #4. + +* **Expectations:** + + * Traffic from ATE1 to ATE2 should be GUE encapsulated with tunnel + destinations `$ATE2_INTERNAL_TE11.v4/32` and `$ATE2_INTERNAL_TE10.v4/32` + and routed out `$DUT_Port4<>$ATE2_Port3`. Verify this on the ATE2. + + * `ATE2_Port3` sends encapsulated flows (Flow-Set #3 and Flow-Set #4) to + `ATE1_Port1` through the DUT. + + BE1 to AF2 flows are expected to have a tunnel destination of + `$DUT_TE11.v4/32`, while AF3 and AF4 flows should have `$DUT_TE10.v4/32` + as their tunnel destination. Traffic should reach the destination + successfully with zero loss. + + * Post-test health checks should be performed and compared against the + baseline. Verify the absence of drops or core dumps. If any, the test + Must fail + +### RT-3.52.8: Negative Scenario - EBGP Route for remote tunnel endpoints Removed + +* **Situation:** + + * The test begins from the final state of RT-3.52.7 In this state, the DUT + encapsulates BE1-AF2 traffic from ATE1 to ATE2 towards tunnel + destination address `"$ATE2_INTERNAL_TE11.v4/32"`, and AF3-AF4 traffic + is encapsulated towards `"$ATE2_INTERNAL_TE10.v4/32"` Similarly, BE1-AF2 + traffic from ATE2 to ATE1 is encapsulated with tunnel destination + `"$DUT_TE11.v4/32"`, and AF3-AF4 traffic uses `"$DUT_TE10.v4/32"` + * ATE2 do not send any unencapsulated flows (Flow-Set#5) + +* **Test Steps:** + + * Execute the previously defined health checks as a baseline + * Flow-Sets #1 through #4 should be active + * On `ATE2_Port3`, stop advertising the prefixes + `"$ATE2_INTERNAL_TE11.v4/32"` and `"$ATE2_INTERNAL_TE10.v4/32"` to + `DUT_Port4` over EBGP + +* **Expectations:** + + * When `ATE2_Port3` withdraws the route advertisement on the EBGP peering + with `DUT_Port4`: + * The tunnel endpoints `"$ATE2_INTERNAL_TE11.v4/32"` and + `"$ATE2_INTERNAL_TE10.v4/32"`, learned via the IBGP peering between + $ATE2_IBGP.v[46]<>$DUT_lo0.v[46], should be placed in the FIB. + * Traffic from ATE1 to ATE2 should then take the path `DUT_Port2` --> + `ATE2_Port1` path after encapsulation on the DUT, with no traffic + loss expected due to this change. Please verify this behavior on the + ATE2. + * Post-test health checks should be performed and compared against the + baseline. Verify the absence of drops or core dumps. If any, the + test Must fail + +### RT-3.52.9: Negative Scenario - IBGP Route for Remote Tunnel Endpoints Removed + +Inflight + +* **Situation:** + + * The test starts from the end state of RT-3.52.8, restart all the flows. + Traffic between ATE1 to ATE2 is routed via the `DUT_Port2` --> + `ATE2_Port1` path after encapsulation on the DUT. ATE2 --> ATE1 traffic + is routed via the ATE2_Port3 --> DUT_Port4 path. + * Static routes for `$ATE2_PPNH1.v6/128` and `$ATE2_PPNH2.v6/128` are + active because tunnel endpoints `"$ATE2_INTERNAL_TE11.v4/32"` and + `"$ATE2_INTERNAL_TE10.v4/32"` are reachable via the IBGP peering between + `$ATE2_IBGP.v[46]` and `$DUT_lo0.v[46]` + * Routes for `ATE2_INTERNAL[6-10].v[46]`, advertised by ATE2 over the IBGP + peering `$ATE2_C.IBGP.v6<>$DUT_lo0.v6`, remain active on the DUT. + * Verify the FIB entries using the AFT streamed data. +``` +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix +``` + +* **Test Steps:** + + * Configure `$ATE2_IBGP.v[46]` to stop advertising tunnel endpoints + `"$ATE2_INTERNAL_TE11.v4/32"` and `"$ATE2_INTERNAL_TE10.v4/32"` to + `$DUT_lo0.v[46]` over their IBGP peering + +* **Expectations:** + + * Static routes for `$ATE2_PPNH1.v6/128` and `$ATE2_PPNH2.v6/128` must + become invalid. Verify using the AFT table + * Routes for `ATE2_INTERNAL[6-10].v[46]` advertised by ATE2 over the IBGP + peering `$ATE2_C.IBGP.v6<>$DUT_lo0.v6` must become invalid. Verify using + the AFT table + * Routes for `ATE2_INTERNAL[6-10].v[46]` advertised by `$ATE2_IBGP.v[46]` + over the IBGP peering `$ATE2_IBGP.v[46]<>$DUT_lo0.v[46]` must be placed + in the FIB. Verify using AFT. + * Traffic from ATE1 to ATE2 towards `ATE2_INTERNAL[6-10].v[46]` + destinations must not experience any drops and should be routed + unencapsulated via the `ATE2_Port1<>DUT_Port2` path. + * Post-test health checks should be performed and compared against the + baseline. Verify the absence of drops or core dumps. If any, the test + Must fail + * AFT paths below to be used for verification. +``` +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/prefix +``` + +### RT-3.52.10: Establish IBGP Peering over EBGP + +* **Test Steps:** + + * Establish all IS-IS adjacencies. Ensure that prefix + `$ATE2_C.IBGP.v6/128` is not advertised from `ATE2_Port1` to + `DUT_Port2`, and prefixes `$DUT_lo0.v[46]` are not advertised from + `DUT_Port2` to `ATE2_Port1`. Validate using AFT entries. + * Run the previously defined health checks. + * On their mutual EBGP session, `DUT_Port4` advertises `$DUT_lo0.v[46]` + and `ATE2_Port3` advertises `$ATE2_C.IBGP.v6/128`, in addition to any + existing exchanges. This establishes the IBGP session between + `$DUT_lo0.v6/128` and `$ATE2_C.IBGP.v6/128` via the EBGP session between + `DUT_Port4` and `ATE2_Port3`. + * Disable the connection between `DUT_Port2` and `ATE2_Port1`. + * Verify that `$ATE2_C.IBGP.v6/128` and `$DUT_lo0.v6/128` exchange the + same prefixes as before, according to the table mentioned earlier over + their IBGP session. + * Start all flows from Flow-Set #1 to Flow-Set #4. + +* **Expectations:** + + * Ensure no packet drops occur after the IBGP transport migration. + Validate on the ATE. + * Packets should be sent encapsulated between `DUT:Port4` and + `ATE2:Port3`. Validate on the ATE. + * Post-test health checks should be performed and compared against the + baseline. Verify the absence of drops or core dumps. If any, the test + Must fail. Following AFT paths to be used for validation +``` +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix + +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/counters/octets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/counters/packets-forwarded +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/next-hop-group +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/origin-protocol +/network-instances/network-instance/afts/ipv6-unicast/ipv4-entry/state/prefix +``` + +## Canonical OC + +### For GUEv1 Encapsulation configuration + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "default" + }, + "name": "default", + "protocols": { + "protocol": [ + { + "config": { + "identifier": "STATIC", + "name": "STATIC" + }, + "identifier": "STATIC", + "name": "STATIC", + "static-routes": { + "static": [ + { + "config": { + "prefix": "fc00:10::1/128" + }, + "next-hop-group": { + "config": { + "name": "ENCAP-NHG-1" + } + }, + "prefix": "fc00:10::1/128" + } + ] + } + } + ] + }, + "static": { + "next-hop-groups": { + "next-hop-group": [ + { + "config": { + "name": "ENCAP-NHG-1" + }, + "name": "ENCAP-NHG-1", + "next-hops": { + "next-hop": [ + { + "config": { + "index": "0" + }, + "index": "0" + } + ] + } + } + ] + }, + "next-hops": { + "next-hop": [ + { + "config": { + "index": "0" + }, + "encap-headers": { + "encap-header": [ + { + "config": { + "index": 0, + "type": "UDPV4" + }, + "index": 0, + "udp-v4": { + "config": { + "dscp": 32, + "dst-ip": "10.50.50.1", + "dst-udp-port": 6080, + "ip-ttl": 255, + "src-ip": "10.5.5.5", + "src-udp-port": 49152 + } + } + } + ] + }, + "index": "0" + } + ] + } + } + } + ] + } +} +``` + +## Canonical OC + +### For GUEv1 Decapsulation configuration + +```json +{ + "defined-sets": { + "ipv4-prefix-sets": { + "ipv4-prefix-set": [ + { + "config": { + "name": "dst_prefix" + }, + "name": "dst_prefix" + } + ] + } + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "default" + }, + "name": "default", + "policy-forwarding": { + "policies": { + "policy": [ + { + "config": { + "policy-id": "decap-policy" + }, + "policy-id": "decap-policy", + "rules": { + "rule": [ + { + "action": { + "config": { + "decapsulate-gre": true + } + }, + "config": { + "sequence-id": 1 + }, + "ipv4": { + "config": { + "destination-address-prefix-set": "dst_prefix", + "protocol": "IP_UDP" + } + }, + "sequence-id": 1, + "transport": { + "config": { + "destination-port": 6080 + } + } + } + ] + } + } + ] + } + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # config + /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name: + /network-instances/network-instance/static/next-hop-groups/next-hop-group/next-hops/next-hop/config/index: + /network-instances/network-instance/static/next-hops/next-hop/config/index: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/config/index: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/config/type: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/dscp: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/dst-ip: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/dst-udp-port: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/ip-ttl: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/src-ip: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/src-udp-port: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hop-group/config/name: + + # telemetry + # BGP + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/state/session-state: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received: + /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent: + + # IS-IS + /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/adjacency-state: + + + # AFT + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol: + /network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol: + /network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix: + + # interface counters + /interfaces/interface/state/counters/out-unicast-pkts: + + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * FFF - fixed form factor diff --git a/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/metadata.textproto b/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/metadata.textproto new file mode 100644 index 00000000000..f5fa5b798c8 --- /dev/null +++ b/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "16e91827-1d4f-4836-8438-d17b387a5ea8" +plan_id: "RT-3.52" +description: "Multidimensional test for Static GUE Encap/Decap based on BGP path selection and selective DSCP marking" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/README.md b/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/README.md new file mode 100644 index 00000000000..ce116858e00 --- /dev/null +++ b/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/README.md @@ -0,0 +1,714 @@ +# PF-1.23: EthoCWoMPLSoGRE IPV4 forwarding of IPV4/IPV6 payload + +## Summary + +This test verifies "EthoCWoMPLSoGRE" forwarding of IP traffic using policy-forwarding configuration. + +"EthoCWoMPLSoGRE" describes the encapsulation for IPv4/IPv6 packets, including the Ethernet header, all contained within GRE and MPLS headers. In addition 4-byte zero control word (CW) is inserted between the MPLS header and the inner Ethernet header. + + +------------------------------------+ + | Outer IP Header | + +------------------------------------+ + | GRE Header | + +------------------------------------+ + | MPLS Label Stack | + +------------------------------------+ + | Control Word (CW) | + | (4 bytes - all '0') | + +------------------------------------+ + | Inner Ethernet Header | + +------------------------------------+ + | Inner IP Header | + +------------------------------------+ + | Layer-4 Header | + +------------------------------------+ + | Payload | + +------------------------------------+ + +## Testbed type + +* [`featureprofiles/topologies/atedut_5.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_5.testbed) + +## Procedure + +### Test environment setup + +```text +DUT has an ingress and 2 egress aggregate interfaces. + + | | --eBGP-- | ATE Ports 2,3 | + [ ATE Ports 1 ]---- | DUT | | | + | | --eBGP-- | ATE Port 4,5 | +``` + +Test uses aggregate 802.3ad bundled interfaces (Aggregate Interfaces). + +* Ingress Port: Traffic is generated from Aggregate1 (ATE Ports 1). + +* Egress Ports: Aggregate2 (ATE Ports 2,3) and Aggregate3 (ATE Ports 4,5) are used as the destination ports for encapsulated traffic. + +### PF-1.23.1: Generate DUT Configuration + +Aggregate 1 "customer interface" is the ingress port that could either have port mode configuration or attachment mode configuration as described below. + +EACH test should be run twice - once with port mode configuration and once with attachment mode configuration. + +#### Aggregate 1 "customer interface" Port mode configuration + +* Configure DUT port 1 to be a member of aggregate interface named "customer interface" +* "customer interface" is a static Layer 2 bundled interface part of pseudowire that accepts packets from all VLANs. +* MTU default 9216 + +#### Aggregate 1 "customer interface" Attachment mode configuration + +* Configure DUT port 1 to be a member of aggregate interface named "customer interface" +* Create a sub interface of the aggregated interface and assign a VLAN ID to it. +* This sub interface will be a static Layer 2 bundled interface part of pseudowire that accepts packets from vlan ID associated with it. +* MTU default 9216 + +#### Policy Forwarding Configuration + +* Policy-forwarding enabling EthoMPLSoGRE encapsulation of all incoming traffic: + + * The forwarding policy must allow forwarding of incoming traffic across 16 tunnels. 16 Tunnels has 16 source address and a single tunnel destination. + + * Source address must be configurable as: + * Loopback address OR + * 16 source addresses corresponding to a single tunnel destinations to achieve maximum entropy. + + * DSCP of the innermost IP packet header must be preserved during encapsulation + + * DSCP of the GRE/outermost IP header must be configurable (Default TOS 96) during encapsulation + + * TTL of the outer GRE must be configurable (Default TTL 64) + + * QoS Hardware queues for all traffic must be configurable (default QoS hardaware class selected is 3) + +### Pseudowire (PW) Configuration + +* "Customer interface" is endpoint 1 and endpoint 2 is an IP address pointing towards Aggregate2, Aggregare3 +* Two unique static MPLS label for local label and remote label. +* Enable control word + +### Aggregate 2 configuration + +* IPV4 and IPV6 addresses + +* MTU (default 9216) + +* LACP Member link configuration + +* Lag id + +* LACP (default: period short) + +* Carrier-delay (default up:3000 down:150) + +* Statistics load interval (default:30 seconds) + +### Routing + +* Create static route for tunnel destination pointing towards Aggregate 2. +* Static mapping of MPLS label for encapsulation must be configurable + +### MPLS Label + +* Entire Label block must be reallocated for static MPLS +* Labels from start/end/mid ranges must be usable and configured corresponding to EthoMPLSoGRE encapsulation + +### PF-1.23.2: Verify PF EthoMPLSoGRE encapsulate action for unencrytped IPv4, IPv6 traffic with entropy on ethernet headers + +* Generate 1000 different traffic flows on ATE Port 1 at line rate with a mix of both IPV4 and IPv6 traffic. Use 64, 128, 256, 512, 1024 MTU bytes frame size. +* Flows should have entropy on Source MAC address, Destination MAC address. Other headers are fixed. + +Verify: + +* All traffic received on Aggregate2, Aggregate3 are EthoCWoMPLSoGRE-encapsulated. +* No packet loss when forwarding. +* Traffic equally load-balanced across 16 GRE destinations and distributed equally across 2 egress ports. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + + +### PF-1.23.3: Verify no hashing of EthoCWoMPLSoGRE encapsulation for unencrytped IPv4 traffic without entropy + +* Generate single traffic flow on ATE Port 1 at line rate with IPV4 traffic. Use 64, 128, 256, 512, 1024 MTU bytes frame size. +* Flows should have NOT have entropy on any headers. + +Verify: + +* All traffic received on either Aggregate2 only or Aggregate3 and is EthoCWoMPLSoGRE-encapsulated. +* No hashing of traffic between Aggregate2 and Aggregate3. +* No packet loss when forwarding. +* Traffic only takes one of the 16 GRE destinations. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + + +### PF-1.23.4: Verify PF EthoCWoMPLSoGRE encapsulate action for MACSec encrytped IPv4, IPv6 traffic + +* Generate 1000 different traffic flows on ATE Port 1 at line rate with a mix of both IPV4 and IPv6 traffic. Use 64, 128, 256, 512, 1024 MTU bytes frame size. +* Flows are MACSec encrypted when sent from ATE Port 1. +* MACSec should encrypt all headers and paylaod of traffic flow, including source mac, destination mac and VLAN tag. +* Flows should have entropy on Source MAC address, Destination MAC address. Other headers are fixed. + +Verify: + +* All traffic received on either Aggregate2 only or Aggregate3 only and is EthoCWoMPLSoGRE-encapsulated. +* No hashing of traffic between Aggregate2 and Aggregate3 +* No packet loss when forwarding. +* Traffic only takes one of the 16 GRE destinations. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### PF-1.23.5: Verify PF EthoCWoMPLSoGRE encapsulate action with Jumbo MTU +* Use the same traffic profile as PF-1.23.2. However, set the packet size to 9000 bytes + +Verify: + +* All traffic received on Aggregate2, Aggregate3 are EthoCWoMPLSoGRE-encapsulated. +* No packet loss when forwarding. +* Traffic equally load-balanced across 16 GRE destinations and distributed equally across 2 egress ports. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### PF-1.23.6: Verify Control word for unencrypted traffic flow + +* Use the same traffic profile as PF-1.23.2. + +Verify: + +* Verify a “0” (32-bit field ) control word is inserted between the MPLS label stack and the Layer 2 payload (the Ethernet frame). +* All traffic received on Aggregate2, Aggregate3 are EthoCWoMPLSoGRE-encapsulated. +* No packet loss when forwarding. +* Traffic equally load-balanced across 16 GRE destinations and distributed equally across 2 egress ports. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### PF-1.23.7: Verify Control word for encrypted traffic flow + +* Use the same traffic profile as PF-1.23.4 + +Verify: + +* Verify a “0” (32-bit field ) control word is inserted between the MPLS label stack and the Layer 2 payload (the Ethernet frame). +* All traffic received on either Aggregate2 only or Aggregate3 only and is EthoCWoMPLSoGRE-encapsulated. +* No hashing of traffic between Aggregate2 and Aggregate3 +* No packet loss when forwarding. +* Traffic only takes one of the 16 GRE destinations. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### PF-1.23.8: Verify DSCP of EthoCWoMPLSoGRE encapsulated packets + +* Use the same traffic profile as PF-1.23.2. + +Verify: + +* TOS of encapsulated packets is set to 96. +* All traffic received on Aggregate2, Aggregate3 are EthoCWoMPLSoGRE-encapsulated. +* No packet loss when forwarding. +* Traffic equally load-balanced across 16 GRE destinations and distributed equally across 2 egress ports. + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### PF-1.23.9: Verify PF EthoCWoMPLSoGRE encapsulate after single GRE tunnel destination shutdown. + +* Use the same traffic profile as PF-1.23.2. +* Start the traffic profile on ATE. +* Shutdown or remove a single GRE tunnel destination on the DUT. + +Verify: + +* All traffic received on Aggregate2, Aggregate3 are EthoCWoMPLSoGRE-encapsulated. +* No packet loss when forwarding. +* Traffic load-balanced across remaining 15 GRE destinations and distributed equally across 2 egress ports. + +### PF-1.23.10: Verify PF EthoCWoMPLSoGRE decapsulate action + +Generate traffic on ATE Aggregate2 and Aggregate3 having: + +* Outer source address: random combination of 1000+ IPV4 source addresses from 100.64.0.0/22 +* Outer destination address: Traffic must fall within the configured GRE tunnel sources in PF-1.23.1 so it cuold be decapsulated. +* MPLS Label: Should be same as the local label configured in PF-1.23.1 +Inner payload: +* Both IPV4 and IPV6 unicast payloads, with random source address, destination address, TCP/UDP source port and destination ports +* Use 64, 128, 256, 512, 1024.. MTU bytes frame size + +* Verify: + +* All traffic received on Aggregate2 and Aggregate3 gets decapsulated and forwarded as IPV4/IPV6 unicast on the respective egress interfaces under Aggregate1 +* No packet loss when forwarding with counters incrementing corresponding to traffic + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + +### PF-1.23.11: Verify VLAN tag after PF EthoCWoMPLSoGRE decapsulate action + +* Use the same traffic profile as PF-1.23.12. +* Ensure inner payload Ethernet header has a VLAN tag attached to it. + +* Verify: + +* In port mode configuration: Traffic flow is decapsulated and mapped to the resperctive pseudowire and egress interface based on the MPLS label. Inner payload VLAN tag is retained after decapsulation. +* In attachment mode configuration: Traffic flow is decapsulated and mapped to the resperctive pseudowire and egress interface based on the MPLS label. VLAN tag of decapsulated packet is same as the VLAN-ID associated with the egress sub-interface. +* No packet loss when forwarding with counters incrementing corresponding to traffic + +Run the test separately for both port mode and attachment mode "customer interface" configuration. + + +## Canonical OC + +Port mode Interface configs + +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "CLOUD-CSI", + "enabled": true, + "mtu": 9080, + "name": "Bundle-Ether8", + "type": "ieee8023adLag" + }, + "name": "Bundle-Ether8", + "rates": { + "config": { + "load-interval": 30 + } + }, + "subinterfaces": { + "subinterface": [ + { + "config": { + "description": "CLOUD-CSI", + "enabled": true, + "index": 0 + }, + "index": 0, + "ipv4": { + "config": { + "mtu": 9066 + } + }, + "ipv6": { + "config": { + "mtu": 9066 + } + } + } + ] + }, + "aggregation": { + "config": { + "lag-type": "STATIC" + } + } + } + ] + } +} +``` + +## Canonical OC + +VLAN mode Interface configs + +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "CLOUD-CSI", + "enabled": true, + "mtu": 9080, + "name": "Bundle-Ether9", + "type": "ieee8023adLag" + }, + "name": "Bundle-Ether9", + "rates": { + "config": { + "load-interval": 30 + } + }, + "subinterfaces": { + "subinterface": [ + { + "config": { + "description": "CLOUD-GEO-PRIVATE [T=qp1309122726287]", + "enabled": true, + "index": 1709 + }, + "index": 1709, + "vlan": { + "match": { + "single-tagged": { + "config": { + "vlan-id": 1709 + } + } + } + } + } + ] + } + } + ] + } +} +``` + +## Canonical OC + +Pseudowire configs Port mode + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "GEO_4", + "config": { + "name": "GEO_4", + "type": "L2P2P" + }, + "connection-points": { + "connection-point": [ + { + "connection-point-id": "GEO_4", + "config": { + "connection-point-id": "GEO_4" + }, + "endpoints": { + "endpoint": [ + { + "endpoint-id": "LOCAL", + "config": { + "endpoint-id": "LOCAL", + "type": "LOCAL" + }, + "local": { + "config": { + "interface": "Bundle-Ether9", + "subinterface": 0 + } + } + }, + { + "endpoint-id": "REMOTE", + "config": { + "endpoint-id": "REMOTE", + "type": "REMOTE" + }, + "remote": { + "config": { + "virtual-circuit-identifier": 4 + } + } + } + ] + } + } + ] + } + } + ] + }, + "interfaces": { + "interface": [ + { + "name": "Bundle-Ether9", + "config": { + "name": "Bundle-Ether9", + "type": "IANA_INTERFACE_TYPE:ieee8023adLag", + "enabled": true + }, + "ethernet": { + "config": { + "aggregate-id": "Bundle-Ether9" + } + }, + "subinterfaces": { + "subinterface": [ + { + "index": 0, + "config": { + "index": 0, + "description": "L2P2P Service GEO_4 - Local Endpoint" + } + } + ] + } + } + ] + } +} +``` + +## Canonical OC + +Pseudowire configs VLAN mode + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "GEO_4", + "config": { + "name": "GEO_4", + "type": "L2P2P" + }, + "connection-points": { + "connection-point": [ + { + "connection-point-id": "GEO_4", + "config": { + "connection-point-id": "GEO_4" + }, + "endpoints": { + "endpoint": [ + { + "endpoint-id": "LOCAL", + "config": { + "endpoint-id": "LOCAL", + "type": "LOCAL" + }, + "local": { + "config": { + "interface": "Bundle-Ether9", + "subinterface": 1709 + } + } + }, + { + "endpoint-id": "REMOTE", + "config": { + "endpoint-id": "REMOTE", + "type": "REMOTE" + }, + "remote": { + "config": { + "virtual-circuit-identifier": 4 + } + } + } + ] + } + } + ] + } + } + ] + }, + "interfaces": { + "interface": [ + { + "name": "Bundle-Ether9", + "config": { + "name": "Bundle-Ether9", + "type": "IANA_INTERFACE_TYPE:ieee8023adLag", + "enabled": true + }, + "ethernet": { + "config": { + "aggregate-id": "Bundle-Ether9" + } + }, + "subinterfaces": { + "subinterface": [ + { + "index": 1709, + "config": { + "index": 1709, + "description": "L2P2P Service GEO_4 - Local Endpoint (VLAN implied by index)" + } + } + ] + } + } + ] + } +} +``` + +## Open Config + +Tunnels/Next-hop group configs + +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "config": { + "name": "DEFAULT" + }, + "static": { + "next-hop-groups": { + "next-hop-group": [ + { + "config": { + "name": "MPLS_in_GRE_Encap" + }, + "name": "MPLS_in_GRE_Encap", + "next-hops": { + "next-hop": [ + { + "index": "1", + "config": { + "index": "1" + } + }, + { + "index": "2", + "config": { + "index": "2" + } + } + ] + } + } + ] + }, + "next-hops": { + "next-hop": [ + { + "index": "1", + "config": { + "index": "1", + "next-hop": "nh_ip_addr_1", + "encap-headers": { + "encap-header": [ + { + "index": "1", + "type": "GRE", + "config": { + "dst-ip": "outer_ipv4_dst_def", + "src-ip": "outer_ipv4_src1", + "dscp": "outer_dscp", + "ip-ttl": "outer_ip-ttl" + } + } + ] + } + } + }, + { + "index": "2", + "config": { + "index": "2", + "next-hop": "nh_ip_addr_2", + "encap-headers": { + "encap-header": [ + { + "index": "2", + "type": "GRE", + "config": { + "dst-ip": "outer_ipv4_dst_def", + "src-ip": "outer_ipv4_src2", + "dscp": "outer_dscp", + "ip-ttl": "outer_ip-ttl" + } + } + ] + } + } + } + ] + } + } + } + ] + } +} +``` +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # interface configs + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + /interfaces/interface/config/mtu: + /interfaces/interface/config/name: + /interfaces/interface/config/type: + /interfaces/interface/rates/config/load-interval: + /interfaces/interface/subinterfaces/subinterface/config/description: + /interfaces/interface/subinterfaces/subinterface/config/enabled: + /interfaces/interface/subinterfaces/subinterface/config/index: + /interfaces/interface/subinterfaces/subinterface/ipv4/config/mtu: + /interfaces/interface/subinterfaces/subinterface/ipv6/config/mtu: + /interfaces/interface/aggregation/config/lag-type: + + # psuedowire configs + /network-instances/network-instance/config/name: + /network-instances/network-instance/config/type: + /network-instances/network-instance/connection-points/connection-point/config/connection-point-id: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/config/endpoint-id: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/interface: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/subinterface: + /network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/remote/config/virtual-circuit-identifier: + + #TODO: Add new OCs for labels and next-hop-group under connection-point + #/network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/local/config/local-label + #/network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/remote/config/remote-label + #/network-instances/network-instance/connection-points/connection-point/endpoints/endpoint/remote/config/next-hop-group + + + #Tunnels/Next-hop group configs + + #TODO: Add new OC for GRE encap headers + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/config/index: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/config/next-hop: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/config/index: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/type: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/dst-ip: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/src-ip: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/dscp: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/ip-ttl: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/index: + + + # Telemetry paths + /interfaces/interface/state/counters/in-discards: + /interfaces/interface/state/counters/in-errors: + /interfaces/interface/state/counters/in-multicast-pkts: + /interfaces/interface/state/counters/in-pkts: + /interfaces/interface/state/counters/in-unicast-pkts: + /interfaces/interface/state/counters/out-discards: + /interfaces/interface/state/counters/out-errors: + /interfaces/interface/state/counters/out-multicast-pkts: + /interfaces/interface/state/counters/out-pkts: + /interfaces/interface/state/counters/out-unicast-pkts: + + /interfaces/interface/subinterfaces/subinterface/state/counters/in-discards: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-errors: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-multicast-pkts: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-pkts: + /interfaces/interface/subinterfaces/subinterface/state/counters/in-unicast-pkts: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-discards: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-errors: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-multicast-pkts: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-pkts: + /interfaces/interface/subinterfaces/subinterface/state/counters/out-unicast-pkts: + + # Config paths for GRE decap + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-gre: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* MFF +* FFF diff --git a/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/metadata.textproto b/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/metadata.textproto new file mode 100644 index 00000000000..aefdb0344ff --- /dev/null +++ b/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "fddf5a4c-ace7-464a-8f2b-70ccf3254b84" +plan_id: "PF-1.23" +description: "EthoCWoMPLSoGRE IPV4 forwarding of IPV4/IPV6 payload" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/add_remove_policy_bound_interface_test/README.md b/feature/policy_forwarding/otg_tests/add_remove_policy_bound_interface_test/README.md new file mode 100644 index 00000000000..913c67fd5d7 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/add_remove_policy_bound_interface_test/README.md @@ -0,0 +1,179 @@ +# PF-1.24: Add and remove interface bound to PBF + +## Summary + +For an interface that is bound to a PBF policy +* Remove an interface bound to a policy +* Add a new interface to the policy +* Remove an interface from both a policy and the device + +## Testbed type + +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Topology: + +```mermaid +graph LR; +A[ATE] <-- Port1(IPv4/6) --> B[DUT]; +A[ATE] <-- Port2(IPv4/6) --> B[DUT]; +``` + +## Procedure + +### Initial setup + +* Connect DUT port-1, 2, 3 to ATE port-1, 2, 3 respectively + +* Configure IPv4/IPv6 addresses on DUT and ATE ports as shown below + + * DUT port-1 IPv6 address ```dp1-v6 = 2001:DB2::1/126``` + * ATE port-1 IPv6 address ```ap1-v6 = 2001:DB2::2/126``` + * DUT port-1 IPv4 address ```dp1-v4 = 192.168.100.1/30``` + * ATE port-1 IPv4 address ```ap1-v4 = 192.168.100.2/30``` + + * DUT port-2 IPv6 address ```dp2-v6 = 2001:DB2::5/126``` + * ATE port-2 IPv6 address ```ap2-v6 = 2001:DB2::6/126``` + * DUT port-2 IPv4 address ```dp1-v4 = 192.168.100.5/30``` + * ATE port-2 IPv4 address ```ap1-v4 = 192.168.100.6/30``` + + * DUT port-3 IPv6 address ```dp2-v6 = 2001:DB2::9/126``` + * ATE port-3 IPv6 address ```ap2-v6 = 2001:DB2::a/126``` + * DUT port-3 IPv4 address ```dp1-v4 = 192.168.100.9/30``` + * ATE port-3 IPv4 address ```ap1-v4 = 192.168.100.10/30``` + +* Create a vrf on DUT `VRF-100` with no ports assigned to it. + +* Configure two IPv6 and two IPv4 static routes on DUT in VRF `VRF-100` towards ATE + * NOTE: It is not necessary for these networks to be configured on ATE. They are just used for traffic destinations + * Destination: `ipv6-net-a = 3008:DB8::/126` with next-hop `ap1-v6 = 2001:DB2::2/126` in vrf `VRF-100` + * Destination: `ipv6-net-b = 3009:DB9::/126` with next-hop `ap2-v6 = 2001:DB2::6/126` in vrf `VRF-100` + * Destination: `ipv4-net-a = 192.168.200.0/24` with next-hop `ap1-v4 = 192.168.100.2/30` in vrf `VRF-100` + * Destination: `ipv4-net-b = 192.168.300.0/24` with next-hop `ap1-v4 = 192.168.100.6/30` in vrf `VRF-100` + * NOTE: Default network instance should not have any static routes configured, above static routes should only be configured in `VRF-100` + +* Configure a forwarding policy type 'VRF_SELECTION_POLICY' + * Rule with sequence `10` to match: + * All IPv6 and IPv4 traffic + * source and destination is set to`ipv6 = ::/0` and `ipv4 = 0.0.0.0/0` + * Set the action to match network instance `VRF-10` + * Apply the policy to DUT Port1 and Port2 + * NOTE: DUT Port1, Port2 and Port3 are in `default vrf` + +### PF-1.24.1 - Remove an interface bound to VRF Selection Policy + +* Remove DUT Port2 from the VRF Selection Policy +* Start traffic from ATE port-3 towards + * `ipv6-net-a = 3008:DB8::/126` + * `ipv6-net-b = 3009:DB9::/126` + * `ipv4-net-a = 192.168.200.0/24` + * `ipv4-net-b = 192.168.300.0/24` +* Validate the traffic is received on ATE Port1 only + +### PF-1.24.2 - Add an interface bound to VRF Selection Policy + +* Add DUT Port2 to the VRF Selection Policy +* Start traffic from ATE port-3 towards + * `ipv6-net-a = 3008:DB8::/126` + * `ipv6-net-b = 3009:DB9::/126` + * `ipv4-net-a = 192.168.200.0/24` + * `ipv4-net-b = 192.168.300.0/24` +* Validate the traffic is received on ATE Port1 and Port2 + +### PF-1.24.3 - Remove an interface from both a policy and the device + +* Use gnmi batch replace to: + * Remove DUT Port2 from the VRF Selection Policy as well as from the device +* Start traffic from ATE port-3 towards + * `ipv6-net-a = 3008:DB8::/126` + * `ipv6-net-b = 3009:DB9::/126` + * `ipv4-net-a = 192.168.200.0/24` + * `ipv4-net-b = 192.168.300.0/24` +* Validate the traffic is received on ATE Port1 only + +### Canonical OC + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "default" + }, + "name": "default", + "policy-forwarding": { + "interfaces": { + "interface": [ + { + "config": { + "apply-forwarding-policy": "vrf100policy", + "interface-id": "eth3/3/1" + }, + "interface-id": "eth3/3/1" + } + ] + }, + "policies": { + "policy": [ + { + "config": { + "policy-id": "vrf100policy", + "type": "VRF_SELECTION_POLICY" + }, + "policy-id": "vrf100policy", + "rules": { + "rule": [ + { + "action": { + "config": { + "network-instance": "vrf100" + } + }, + "config": { + "sequence-id": 10 + }, + "sequence-id": 10 + } + ] + } + } + ] + } + } + }, + { + "config": { + "name": "vrf100", + "type": "L3VRF" + }, + "name": "vrf100" + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # forwarding-policy paths + /network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: + /network-instances/network-instance/policy-forwarding/policies/policy/config/type: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/sequence-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/source-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/config/destination-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/network-instance: + # apply policy to the interface + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-forwarding-policy: + +rpcs: + gnmi: + gNMI.Set: + replace: true +``` + +## Minimum DUT platform requirement + +FFF diff --git a/feature/policy_forwarding/otg_tests/add_remove_policy_bound_interface_test/metadata.textproto b/feature/policy_forwarding/otg_tests/add_remove_policy_bound_interface_test/metadata.textproto new file mode 100644 index 00000000000..cebddc90818 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/add_remove_policy_bound_interface_test/metadata.textproto @@ -0,0 +1,34 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "93de65a7-8566-4106-96c2-91d3ea82545e" +plan_id: "PF-1.24" +description: "Add and remove interface bound to PBF" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + static_protocol_name: "static" + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + static_protocol_name: "STATIC" + interface_enabled: true + default_network_instance: "default" + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + interface_enabled: true + } +} diff --git a/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/README.md b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/README.md index a1ebcd45420..8e2975411de 100644 --- a/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/README.md +++ b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/README.md @@ -94,4 +94,4 @@ rpcs: ## Required DUT platform * MFF - * FFF \ No newline at end of file + * FFF diff --git a/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/match_dscp_indirect_next_hop_test.go b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/match_dscp_indirect_next_hop_test.go new file mode 100644 index 00000000000..15a03349c0d --- /dev/null +++ b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/match_dscp_indirect_next_hop_test.go @@ -0,0 +1,701 @@ +// Copyright 2022 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 match_dscp_indirect_next_hop_test implements PF-1.1: IPv4/IPv6 policy-forwarding to indirect NH matching DSCP/TC. +package match_dscp_indirect_next_hop_test + +import ( + "context" + "fmt" + "math" + "net" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + advertisedRoutesv4Prefix = 32 + advertisedRoutesv6Prefix = 128 + dutAS = 65500 + plenIPv4 = 30 + plenIPv6 = 126 + rplType = oc.RoutingPolicy_PolicyResultType_ACCEPT_ROUTE + rplName = "ALLOW" + peerGrpNamev4 = "BGP-PEER-GROUP-V4" + peerGrpNamev6 = "BGP-PEER-GROUP-V6" + RouteCount = uint32(1) + ateAS = 65501 + ipvNHv4 = "202.0.113.1" + ipvNHv6 = "2001:FFFF::1234:5678" + ipv4Dst = "100.100.100.0" + ipv6Dst = "2001:2:1::0" + lossTolerance = 1 + bgpV4RouteName = "BGP4-PEER-v4ROUTE" + bgpV6RouteName = "BGP6-PEER-v6ROUTE" + policyMatchFlowV4 = "PF-MATCH-FLOW-V4" + policyMatchFlowV6 = "PF-MATCH-FLOW-V6" + policyNoMatchFlowV4 = "PF-NO-MATCH-FLOW-V4" + policyNoMatchFlowV6 = "PF-NO-MATCH-FLOW-V6" + defaultFlow = "DEFAULT-FLOW" + defaultFlowV6 = "DEFAULT-FLOW-V6" + trafficPolicyName = "BG_PBR_TRAFFIC_POLICY" + trafficDuration = 30 * time.Second + timeout = 1 * time.Minute + interval = 20 * time.Second + iterationCount = 2 +) + +var ( + dutP1 = attrs.Attributes{ + Name: "port1", + Desc: "DUT to ATE source", + IPv4: "192.0.2.1", + IPv6: "2001:db8::1", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + ateP1 = attrs.Attributes{ + Name: "ateP1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + dutP2 = attrs.Attributes{ + Name: "port2", + Desc: "DUT to ATE destination-2", + IPv4: "192.0.2.5", + IPv6: "2001:db8::5", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + ateP2 = attrs.Attributes{ + Name: "ateP2", + MAC: "02:00:02:01:01:02", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + dutP3 = attrs.Attributes{ + Name: "port3", + Desc: "DUT to ATE destination-3", + IPv4: "192.0.2.9", + IPv6: "2001:db8::9", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + ateP3 = &attrs.Attributes{ + Name: "ateP3", + MAC: "02:00:02:01:01:03", + IPv4: "192.0.2.10", + IPv6: "2001:db8::a", + IPv4Len: plenIPv4, + IPv6Len: plenIPv6, + } + + pfMatchingDscpValues = []uint32{3, 11, 19, 27, 35, 43, 51, 59} + otherDscpValues = []uint32{0, 8, 16, 24, 32, 40, 48, 56} +) + +type ipAddr struct { + address string + prefix uint32 +} + +// configureDUT configures all the interfaces and BGP on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dc := gnmi.OC() + + for _, portAttr := range []attrs.Attributes{dutP1, dutP2, dutP3} { + p := dut.Port(t, portAttr.Name).Name() + i := portAttr.NewOCInterface(p, dut) + gnmi.Replace(t, dut, dc.Interface(p).Config(), i) + } + + t.Log("Configure Default Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + configureRoutePolicy(t, dut, rplName, rplType) + + dutConfPath := dc.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + dutConf := createBGPNeighbor(dutAS, ateAS, dut) + gnmi.Replace(t, dut, dutConfPath.Config(), dutConf) + + t.Log("Configure Static Routes IPV4-DST1/IPV6-DST1 towards ATE port 3") + configureDUTStaticRoutes(t, dut) + + t.Log("PF action is to redirect to BGP-announced next-hops (IPV-NH-V4/IPV-NH-V6)") + configTrafficPolicy(t, dut, trafficPolicyName) +} + +func configTrafficPolicy(t *testing.T, dut *ondatra.DUTDevice, name string) { + + interfaceName := dut.Port(t, "port1").Name() + + if deviations.PolicyForwardingToNextHopOcUnsupported(dut) { + gnmiClient := dut.RawAPIs().GNMI(t) + config := trafficPolicyConf(dut, interfaceName) + if config == "" { + t.Fatalf("Unsupported vendor %s for deviation 'PolicyForwardingToNextHopOcUnsupported'", dut.Vendor()) + } + t.Logf("Push the CLI config:%s", dut.Vendor()) + gpbSetRequest, err := helpers.BuildCliConfigRequest(config) + if err != nil { + t.Fatalf("Cannot build a gNMI SetRequest: %v", err) + } + if _, err = gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } + } else { + d := &oc.Root{} + ni := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + npf := ni.GetOrCreatePolicyForwarding() + np := npf.GetOrCreatePolicy(name) + np.SetPolicyId(name) + np.Type = oc.Policy_Type_PBR_POLICY + + for i, dscp := range pfMatchingDscpValues { + npRule := np.GetOrCreateRule(uint32(i + 1)) + ip := npRule.GetOrCreateIpv4() + ip.SetSourceAddress(fmt.Sprintf("%s/32", ateP1.IPv4)) + ip.SetDestinationAddress(fmt.Sprintf("%s/32", ipv4Dst)) + ip.SetDscp(uint8(dscp)) + npRuleAction := npRule.GetOrCreateAction() + npRuleAction.SetNextHop(ipvNHv4) + } + + for i, dscp := range pfMatchingDscpValues { + npRule := np.GetOrCreateRule(uint32(i+1) + uint32(len(pfMatchingDscpValues))) + ip := npRule.GetOrCreateIpv6() + ip.SetSourceAddress(fmt.Sprintf("%s/128", ateP1.IPv6)) + ip.SetDestinationAddress(fmt.Sprintf("%s/128", ipv6Dst)) + ip.SetDscp(uint8(dscp)) + npRuleAction := npRule.GetOrCreateAction() + npRuleAction.SetNextHop(ipvNHv6) + } + + interfaceName := dut.Port(t, "port1").Name() + npi := npf.GetOrCreateInterface(interfaceName) + npi.ApplyForwardingPolicy = np.PolicyId + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Config(), ni) + + } +} + +func (ip *ipAddr) cidr(t *testing.T) string { + _, net, err := net.ParseCIDR(fmt.Sprintf("%s/%d", ip.address, ip.prefix)) + if err != nil { + t.Fatal(err) + } + return net.String() +} + +func configureDUTStaticRoutes(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + + prefix := ipAddr{address: ipv4Dst, prefix: 24} + b := &gnmi.SetBatch{} + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: prefix.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(ateP3.IPv4), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + + prefixV6 := ipAddr{address: ipv6Dst, prefix: 48} + sV6 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: prefixV6.cidr(t), + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString(ateP3.IPv6), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV6, dut); err != nil { + t.Fatalf("Failed to configure IPv6 static route: %v", err) + } + b.Set(t, dut) +} + +func trafficPolicyConf(dut *ondatra.DUTDevice, interfaceName string) string { + switch dut.Vendor() { + case ondatra.ARISTA: + var v4MatchRules, v6MatchRules string + // Create MatchingRules And Action + for _, dscp := range pfMatchingDscpValues { + v4MatchRules += fmt.Sprintf(` + match v4-dscp%d ipv4 + dscp %d + ! + actions + count + redirect next-hop group NH_V4 + ! + `, dscp, dscp) + + v6MatchRules += fmt.Sprintf(` + match v6-dscp%d ipv6 + dscp %d + ! + actions + count + redirect next-hop group NH_V6 + ! + `, dscp, dscp) + } + + // Apply Policy on the interface + trafficPolicyConfig := fmt.Sprintf(` + traffic-policies + traffic-policy %s + %s + %s + match ipv4-all-default ipv4 + actions + count + ! + match ipv6-all-default ipv6 + actions + count + ! + nexthop-group NH_V4 type ip + fec hierarchical + entry 0 nexthop %s + ! + nexthop-group NH_V6 type ip + fec hierarchical + entry 0 nexthop %s + ! + interface %s + + traffic-policy input BG_PBR_TRAFFIC_POLICY + `, trafficPolicyName, v4MatchRules, v6MatchRules, ipvNHv4, ipvNHv6, interfaceName) + return trafficPolicyConfig + default: + return "" + } +} + +type BGPNeighbor struct { + as uint32 + neighborip string + isV4 bool +} + +func createBGPNeighbor(localAs, peerAs uint32, dut *ondatra.DUTDevice) *oc.NetworkInstance_Protocol { + nbrs := []*BGPNeighbor{ + {as: peerAs, neighborip: ateP2.IPv4, isV4: true}, + {as: peerAs, neighborip: ateP2.IPv6, isV4: false}, + } + + d := &oc.Root{} + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + niProto := ni1.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := niProto.GetOrCreateBgp() + + global := bgp.GetOrCreateGlobal() + global.SetAs(localAs) + global.SetRouterId(dutP2.IPv4) + + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).SetEnabled(true) + global.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).SetEnabled(true) + + // Note: we have to define the peer group even if we aren't setting any policy because it's + // invalid OC for the neighbor to be part of a peer group that doesn't exist. + pgv4 := bgp.GetOrCreatePeerGroup(peerGrpNamev4) + pgv4.SetPeerAs(peerAs) + pgv4.SetPeerGroupName(peerGrpNamev4) + pgv6 := bgp.GetOrCreatePeerGroup(peerGrpNamev6) + pgv6.SetPeerAs(peerAs) + pgv6.SetPeerGroupName(peerGrpNamev6) + + for _, nbr := range nbrs { + if nbr.isV4 { + nv4 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv4.SetPeerAs(nbr.as) + nv4.SetEnabled(true) + nv4.SetPeerGroup(peerGrpNamev4) + afisafi := nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + afisafi.SetEnabled(true) + nv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST).SetEnabled(false) + pgafv4 := pgv4.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + pgafv4.SetEnabled(true) + rpl := pgafv4.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } else { + nv6 := bgp.GetOrCreateNeighbor(nbr.neighborip) + nv6.SetPeerAs(nbr.as) + nv6.SetEnabled(true) + nv6.SetPeerGroup(peerGrpNamev6) + afisafi6 := nv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + afisafi6.SetEnabled(true) + nv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).SetEnabled(false) + pgafv6 := pgv6.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + pgafv6.SetEnabled(true) + rpl := pgafv6.GetOrCreateApplyPolicy() + rpl.ImportPolicy = []string{rplName} + rpl.ExportPolicy = []string{rplName} + } + } + return niProto +} + +func configureRoutePolicy(t *testing.T, dut *ondatra.DUTDevice, name string, pr oc.E_RoutingPolicy_PolicyResultType) { + d := &oc.Root{} + rp := d.GetOrCreateRoutingPolicy() + pd := rp.GetOrCreatePolicyDefinition(name) + st, err := pd.AppendNewStatement("id-1") + if err != nil { + t.Fatal(err) + } + st.GetOrCreateActions().PolicyResult = pr + gnmi.Replace(t, dut, gnmi.OC().RoutingPolicy().Config(), rp) +} + +func waitForBGPSession(t *testing.T, dut *ondatra.DUTDevice, wantEstablished bool) { + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + nbrPath := statePath.Neighbor(ateP2.IPv4) + nbrPathv6 := statePath.Neighbor(ateP2.IPv6) + compare := func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, ok := val.Val() + if ok { + if wantEstablished { + return state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + } + return state == oc.Bgp_Neighbor_SessionState_IDLE + } + return false + } + + _, ok := gnmi.Watch(t, dut, nbrPath.SessionState().State(), 2*time.Minute, compare).Await(t) + if !ok { + fptest.LogQuery(t, "BGP reported state", nbrPath.State(), gnmi.Get(t, dut, nbrPath.State())) + if wantEstablished { + t.Fatal("No BGP neighbor formed...") + } else { + t.Fatal("BGPv4 session didn't teardown.") + } + } + _, ok = gnmi.Watch(t, dut, nbrPathv6.SessionState().State(), 2*time.Minute, compare).Await(t) + if !ok { + fptest.LogQuery(t, "BGPv6 reported state", nbrPathv6.State(), gnmi.Get(t, dut, nbrPathv6.State())) + if wantEstablished { + t.Fatal("No BGPv6 neighbor formed...") + } else { + t.Fatal("BGPv6 session didn't teardown.") + } + } +} + +func verifyPrefixesTelemetryV4(t *testing.T, dut *ondatra.DUTDevice, wantInstalled uint32) { + t.Helper() + statePath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + prefixesv4 := statePath.Neighbor(ateP2.IPv4).AfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST).Prefixes() + + if gotInstalled, ok := gnmi.Watch(t, dut, prefixesv4.Installed().State(), time.Minute, func(v *ygnmi.Value[uint32]) bool { + got, ok := v.Val() + return ok && got == wantInstalled + }).Await(t); !ok { + t.Errorf("Installed prefixes mismatch: got %v, want %v", gotInstalled, wantInstalled) + } +} + +func configureATE(t *testing.T) gosnappi.Config { + dut := ondatra.DUT(t, "dut") + + config := gosnappi.NewConfig() + ate := ondatra.ATE(t, "ate") + + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + ateP1.AddToOTG(config, ap1, &dutP1) + + d2 := ateP2.AddToOTG(config, ap2, &dutP2) + + d2ipv41 := d2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + d2ipv61 := d2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + configureBGPDev(d2, d2ipv41, ateAS, ipvNHv4) + configureBGPV6Dev(d2, d2ipv61, ateAS, ipvNHv6) + + ateP3.AddToOTG(config, ap3, &dutP3) + + macAddress := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + + configureFlow(config, policyMatchFlowV4, ap1.ID(), + []string{ap2.ID()}, + macAddress, + ateP1.IPv4, + ipv4Dst, "ipv4", 1, pfMatchingDscpValues) + + configureFlow(config, policyNoMatchFlowV4, ap1.ID(), + []string{ap3.ID()}, + macAddress, + ateP1.IPv4, + ipv4Dst, "ipv4", 1, otherDscpValues) + + configureFlow(config, defaultFlow, ap1.ID(), + []string{ap3.ID()}, + macAddress, + ateP1.IPv4, + ipv4Dst, "ipv4", 1, append(pfMatchingDscpValues, otherDscpValues...)) + + configureFlow(config, policyMatchFlowV6, ap1.ID(), + []string{ap2.ID()}, + macAddress, + ateP1.IPv6, + ipv6Dst, "ipv6", 1, pfMatchingDscpValues) + + configureFlow(config, policyNoMatchFlowV6, ap1.ID(), + []string{ap3.ID()}, + macAddress, + ateP1.IPv6, + ipv6Dst, "ipv6", 1, otherDscpValues) + + configureFlow(config, defaultFlowV6, ap1.ID(), + []string{ap3.ID()}, + macAddress, + ateP1.IPv6, + ipv6Dst, "ipv6", 1, append(pfMatchingDscpValues, otherDscpValues...)) + + return config +} + +// configureBGPDev configures the BGP on the OTG dev +func configureBGPDev(dev gosnappi.Device, Ipv4 gosnappi.DeviceIpv4, as int, bgpRoute string) { + + Bgp := dev.Bgp().SetRouterId(Ipv4.Address()) + Bgp4Peer := Bgp.Ipv4Interfaces().Add().SetIpv4Name(Ipv4.Name()).Peers().Add().SetName(dev.Name() + ".BGP4.peer") + Bgp4Peer.SetPeerAddress(dutP2.IPv4).SetAsNumber(uint32(as)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + routes := Bgp4Peer.V4Routes().Add().SetName(bgpV4RouteName) + routes.Addresses().Add(). + SetAddress(bgpRoute). + SetPrefix(advertisedRoutesv4Prefix). + SetCount(RouteCount) + +} + +func configureBGPV6Dev(dev gosnappi.Device, Ipv6 gosnappi.DeviceIpv6, as int, bgpRoutev6 string) { + + // BGP Router Id is always ipv4 + Bgp := dev.Bgp().SetRouterId(ateP2.IPv4) + Bgp6Peer := Bgp.Ipv6Interfaces().Add().SetIpv6Name(Ipv6.Name()).Peers().Add().SetName(dev.Name() + ".BGP6.peer") + Bgp6Peer.SetPeerAddress(dutP2.IPv6).SetAsNumber(uint32(as)).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + routes := Bgp6Peer.V6Routes().Add().SetName(bgpV6RouteName) + routes.Addresses().Add(). + SetAddress(bgpRoutev6). + SetPrefix(advertisedRoutesv6Prefix). + SetCount(RouteCount) + +} + +func configureFlow(topo gosnappi.Config, name, src string, dst []string, dstMac, srcIp, dstIp, iptype string, routeCount uint32, dscp []uint32) { + flow := topo.Flows().Add().SetName(name) + flow.Metrics().SetEnable(true) + flow.TxRx().Port(). + SetTxName(src). + SetRxNames(dst) + flow.Size().SetFixed(1500) + flow.Rate().SetPps(100) + e := flow.Packet().Add().Ethernet() + e.Dst().SetValue(dstMac) + if iptype == "ipv4" { + v4 := flow.Packet().Add().Ipv4() + v4.Src().SetValue(srcIp) + v4.Dst().Increment().SetStart(dstIp).SetCount(routeCount) + v4.Priority().Dscp().Phb().SetValues(dscp) + } else { + v6 := flow.Packet().Add().Ipv6() + v6.Src().SetValue(srcIp) + v6.Dst().Increment().SetStart(dstIp).SetCount(routeCount) + var newValue []uint32 + // IPv6 TC value as per test README is 4*IPv4 DSCP + // Ex: for DSCP [0, 8, 16, 24, 32, 40, 48, 56] IPv6 flows should use TC 8-bit values [0, 32 , 64( , 96, 128, 160, 192, 224]) + for _, i := range dscp { + newValue = append(newValue, i<<2) + } + v6.TrafficClass().SetValues(newValue) + } +} + +func verifyFlowTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, flowName string) bool { + t.Log("Verify Flow Traffic") + startTime := time.Now() + count := 0 + var got float64 + for time.Since(startTime) < timeout { + + otgutils.LogFlowMetrics(t, ate.OTG(), config) + countersPath := gnmi.OTG().Flow(flowName).Counters() + framesRx := gnmi.Get(t, ate.OTG(), countersPath.InPkts().State()) + framesTx := gnmi.Get(t, ate.OTG(), countersPath.OutPkts().State()) + + if got = (math.Abs(float64(framesTx)-float64(framesRx)) * 100) / float64(framesTx); got <= lossTolerance { + return true + } else { + time.Sleep(interval) + count += 1 + } + } + + if count >= iterationCount { + t.Logf("Packet loss percentage for flow: got %v, want %v", got, lossTolerance) + return false + } + return true +} + +func TestPolicyForwardingIndirectNextHop(t *testing.T) { + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + + // DUT Configuration + t.Log("Start DUT interface Config") + configureDUT(t, dut) + + // ATE Configuration. + t.Log("Start ATE Config") + config := configureATE(t) + + otg.PushConfig(t, config) + + otgutils.WaitForARP(t, otg, config, "ipv4") + otgutils.WaitForARP(t, otg, config, "ipv6") + + otg.StartProtocols(t) + + t.Log("Verify BGP Session formed with Port 2") + waitForBGPSession(t, dut, true) + + t.Log("Verifying the Next-Hop for PF is advertised by BGP on Port2") + verifyPrefixesTelemetryV4(t, dut, 1) + + otg.StartTraffic(t) + time.Sleep(trafficDuration) + otg.StopTraffic(t) + + type flowTest struct { + tcName string + ipType string + flow string + } + + tests := []flowTest{ + { + tcName: "PF next-hop", + ipType: "IPv4", + flow: policyMatchFlowV4, + }, + { + tcName: "PF next-hop", + ipType: "IPv6", + flow: policyMatchFlowV6, + }} + for _, tc := range tests { + t.Run("VerifyPFNext_hopAction", func(t *testing.T) { + if verifyFlowTraffic(t, ate, config, tc.flow) { + t.Logf("%s action Passed for %s", tc.tcName, tc.ipType) + } else { + t.Errorf("%s action Failed for %s", tc.tcName, tc.ipType) + } + }) + } + + tests = []flowTest{ + { + tcName: "PF no-match", + ipType: "IPv4", + flow: policyMatchFlowV4, + }, + { + tcName: "PF no-match", + ipType: "IPv6", + flow: policyNoMatchFlowV6, + }} + for _, tc := range tests { + t.Run("VerifyPFNo_matchAction", func(t *testing.T) { + if verifyFlowTraffic(t, ate, config, tc.flow) { + t.Logf("%s action Passed for %s", tc.tcName, tc.ipType) + } else { + t.Errorf("%s action Failed for %s", tc.tcName, tc.ipType) + } + }) + } + + t.Log("PF-1.1.3: Verify PF without NH present Validation in progress") + t.Log("Withdraw next-hop prefixes from BGP Announcement") + cs := gosnappi.NewControlState() + cs.Protocol().Route().SetNames([]string{bgpV4RouteName, bgpV6RouteName}).SetState(gosnappi.StateProtocolRouteState.WITHDRAW) + otg.SetControlState(t, cs) + + verifyPrefixesTelemetryV4(t, dut, 0) + + otg.StartTraffic(t) + time.Sleep(trafficDuration) + otg.StopTraffic(t) + + tests = []flowTest{ + { + tcName: "PF without NH", + ipType: "IPv4", + flow: defaultFlow, + }, + { + tcName: "PF without NH", + ipType: "IPv6", + flow: defaultFlowV6, + }} + for _, tc := range tests { + t.Run("VerifyPFWithoutNHPresent", func(t *testing.T) { + if verifyFlowTraffic(t, ate, config, tc.flow) { + t.Logf("%s action Passed for %s", tc.tcName, tc.ipType) + } else { + t.Errorf("%s action Failed for %s", tc.tcName, tc.ipType) + } + }) + } + +} diff --git a/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/metadata.textproto b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/metadata.textproto new file mode 100644 index 00000000000..33b9fffbb01 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/match_dscp_indirect_next_hop/metadata.textproto @@ -0,0 +1,17 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1a84121f-41a6-4a99-970a-e195476fef08" +plan_id: "PF-1.1" +description: "IPv4/IPv6 policy-forwarding to indirect NH matching DSCP/TC." +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + policy_forwarding_to_next_hop_oc_unsupported: true + } +} \ No newline at end of file diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/README.md index 0aea78d912a..cbffcf6d437 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/README.md @@ -1,4 +1,4 @@ -# PF-1.13 - MPLSoGRE IPV4 decapsulation of IPV4/IPV6 payload scale test +# PF-1.13: MPLSoGRE IPV4 decapsulation of IPV4/IPV6 payload scale test ## Summary This test verifies scaling of MPLSoGRE decapsulation of IP traffic using static MPLS LSP configuration. MPLSoGRE Traffic on ingress to the DUT is decapsulated and IPV4/IPV6 payload is forwarded towards the IPV4/IPV6 egress nexthop. @@ -72,4 +72,4 @@ rpcs: ## Minimum DUT platform requirement -FFF \ No newline at end of file +FFF diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/metadata.textproto new file mode 100644 index 00000000000..66c5b159182 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_scale_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "75187e56-1eea-4e6b-aea1-cb7d3df77168" +plan_id: "PF-1.13" +description: "MPLSoGRE IPV4 decapsulation of IPV4/IPV6 payload scale test" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/README.md index e09444d1b66..efd40991ea3 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/README.md @@ -1,4 +1,4 @@ -# PF-1.12 - MPLSoGRE IPV4 decapsulation of IPV4/IPV6 payload +# PF-1.12: MPLSoGRE IPV4 decapsulation of IPV4/IPV6 payload ## Summary This test verifies MPLSoGRE decapsulation of IP traffic using static MPLS LSP configuration. MPLSoGRE Traffic on ingress to the DUT is decapsulated and IPV4/IPV6 payload is forwarded towards the IPV4/IPV6 egress nexthop. @@ -25,7 +25,7 @@ Test uses aggregate 802.3ad bundled interfaces (Aggregate Interfaces). * Egress Ports: Aggregate1 * Traffic is forwarded (egress) on Aggregate1 (ATE Ports 1,2) . -## PF-1.12.1 Generate config for MPLS in GRE decap and push to DUT +## PF-1.12.1: Generate config for MPLS in GRE decap and push to DUT #### Configuration #### Aggregate1 is the egress port having following configuration: @@ -336,4 +336,4 @@ rpcs: ## Minimum DUT platform requirement -FFF \ No newline at end of file +FFF diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/metadata.textproto new file mode 100644 index 00000000000..858461682b6 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2beaac46-9b7b-49c4-9bde-62ad530aa5c7" +plan_id: "PF-1.12" +description: "MPLSoGRE IPV4 decapsulation of IPV4/IPV6 payload" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + local_proxy_unsupported: true + qos_classification_unsupported: true + policy_forwarding_unsupported: true + mpls_unsupported: true + mpls_label_classification_unsupported: true + aggregate_atomic_update: true + interface_enabled: true + gue_gre_decap_unsupported: true + static_mpls_unsupported: true + default_network_instance: "default" + } +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/mpls_gre_ipv4_decap_test.go b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/mpls_gre_ipv4_decap_test.go new file mode 100644 index 00000000000..42755febf92 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_decap_test/mpls_gre_ipv4_decap_test.go @@ -0,0 +1,535 @@ +// Package mpls_gre_ipv4_decap_test tests mplsogre decap functionality. +package mpls_gre_ipv4_decap_test + +import ( + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + otgconfighelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_config_helpers" + otgvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_validation_helpers" + packetvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/packetvalidationhelpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygot/ygot" +) + +// TestMain calls main function. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag +) + +var ( + top = gosnappi.NewConfig() + aggID string + custPorts = []string{"port1", "port2"} + corePorts = []string{"port3", "port4"} + custIntfIPv4 = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 1500, + IPv4: "169.254.0.11", + IPv4Len: 29, + Subinterface: 20, + } + custIntfIPv6 = attrs.Attributes{ + Desc: "Customer_connectv6", + MTU: 1500, + IPv6: "2600:2d00:0:1:8000:10:0:ca31", + IPv6Sec: "2600:2d00:0:1:8000:10:0:ca33", + IPv6Len: 125, + Subinterface: 21, + } + + custIntfdualStack = attrs.Attributes{ + Desc: "Customer_connect_dualstack", + MTU: 1500, + IPv4: "169.254.0.27", + IPv4Len: 29, + IPv6: "2600:2d00:0:1:7000:10:0:ca31", + IPv6Sec: "2600:2d00:0:1:7000:10:0:ca33", + IPv6Len: 125, + Subinterface: 22, + } + custIntfIPv4MultiCloud = attrs.Attributes{ + Desc: "Customer_connect_multicloud", + MTU: 1500, + IPv4: "169.254.0.33", + IPv4Len: 30, + Subinterface: 23, + } + custIntfIPv4JumboMTU = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 9066, + IPv4: "169.254.0.53", + IPv4Len: 29, + Subinterface: 26, + } + coreIntf = attrs.Attributes{ + Desc: "Core_Interface", + IPv4: "194.0.2.1", + IPv6: "2001:10:1:6::1", + MTU: 9202, + IPv4Len: 24, + IPv6Len: 126, + } + + agg1 = &otgconfighelpers.Port{ + Name: "Port-Channel1", + AggMAC: "02:00:01:01:01:07", + Interfaces: []*otgconfighelpers.InterfaceProperties{interface1, interface2, interface3, interface4, interface8}, + MemberPorts: []string{"port1", "port2"}, + LagID: 1, + IsLag: true, + } + agg2 = &otgconfighelpers.Port{ + Name: "Port-Channel2", + AggMAC: "02:00:01:01:01:01", + MemberPorts: []string{"port3", "port4"}, + Interfaces: []*otgconfighelpers.InterfaceProperties{interface7}, + LagID: 2, + IsLag: true, + } + + interface1 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.12", + IPv4Gateway: "169.254.0.11", + Name: "Port-Channel1.20", + MAC: "02:00:01:01:01:08", + Vlan: 20, + IPv4Len: 29, + } + interface2 = &otgconfighelpers.InterfaceProperties{ + IPv6: "2600:2d00:0:1:8000:10:0:ca32", + IPv6Gateway: "2600:2d00:0:1:8000:10:0:ca31", + MAC: "02:00:01:01:01:09", + Name: "Port-Channel1.21", + Vlan: 21, + IPv6Len: 125, + } + interface3 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.26", + IPv4Gateway: "169.254.0.27", + IPv6: "2600:2d00:0:1:7000:10:0:ca32", + IPv6Gateway: "2600:2d00:0:1:7000:10:0:ca31", + MAC: "02:00:01:01:01:10", + Name: "Port-Channel1.22", + Vlan: 22, + IPv4Len: 29, + IPv6Len: 125, + } + interface4 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.34", + IPv4Gateway: "169.254.0.33", + Name: "Port-Channel1.23", + MAC: "02:00:01:01:01:11", + Vlan: 23, + IPv4Len: 30, + } + interface8 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.54", + IPv4Gateway: "169.254.0.53", + Name: "Port-Channel1.26", + MAC: "02:00:01:01:01:13", + Vlan: 26, + IPv4Len: 29, + } + interface7 = &otgconfighelpers.InterfaceProperties{ + IPv4: "194.0.2.2", + IPv6: "2001:10:1:6::2", + IPv4Gateway: "194.0.2.1", + IPv6Gateway: "2001:10:1:6::1", + Name: "Port-Channel2", + MAC: "02:00:01:01:01:02", + IPv4Len: 29, + IPv6Len: 126, + } + // Custom IMIX settings for all flows. + sizeWeightProfile = []otgconfighelpers.SizeWeightPair{ + {Size: 64, Weight: 20}, + {Size: 128, Weight: 20}, + {Size: 256, Weight: 20}, + {Size: 512, Weight: 10}, + {Size: 1500, Weight: 28}, + {Size: 9000, Weight: 2}, + } + + flowResolveArp = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name}}, + } + nextHopResolutionIPv4 = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[0].Name, agg1.Interfaces[2].Name, agg1.Interfaces[3].Name, agg1.Interfaces[4].Name}}, + } + nextHopResolutionIPv6 = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[1].Name, agg1.Interfaces[2].Name}}, + } + // FlowOuterIPv4 Decap IPv4 Interface IPv4 Payload traffic params Outer Header. + FlowOuterIPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg2.Name + ".IPv4"}, + RxNames: []string{agg1.Interfaces[0].Name + ".IPv4"}, + SizeWeightProfile: &sizeWeightProfile, + Flowrate: 45, + FlowName: "MPLSOGRE traffic IPv4 interface IPv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg2.AggMAC}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "100.64.0.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 10000}, + MPLSFlow: &otgconfighelpers.MPLSFlowParams{MPLSLabel: 99991, MPLSExp: 7}, + GREFlow: &otgconfighelpers.GREFlowParams{Protocol: otgconfighelpers.IanaMPLSEthertype}, + } + // FlowOuterIPv4Validation MPLSOGRE traffic IPv4 interface IPv4 Payload. + FlowOuterIPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[0].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowOuterIPv4.FlowName, TolerancePct: 0.5}, + } + // FlowInnerIPv4 Inner Header IPv4 Payload. + FlowInnerIPv4 = &otgconfighelpers.Flow{ + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "22.1.1.1", IPv4Dst: "21.1.1.1", IPv4SrcCount: 10000, RawPriority: 0, RawPriorityCount: 255}, + TCPFlow: &otgconfighelpers.TCPFlowParams{TCPSrcPort: 49152, TCPDstPort: 80, TCPSrcCount: 10000}, + } + // FlowOuterIPv6 Decap IPv6 Interface IPv6 Payload traffic params Outer Header. + FlowOuterIPv6 = &otgconfighelpers.Flow{ + TxNames: []string{agg2.Name + ".IPv4"}, + RxNames: []string{agg1.Interfaces[1].Name + ".IPv6"}, + SizeWeightProfile: &sizeWeightProfile, + Flowrate: 45, + FlowName: "MPLSOGRE traffic IPv6 interface IPv6 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg2.AggMAC}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "100.64.0.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 10000}, + MPLSFlow: &otgconfighelpers.MPLSFlowParams{MPLSLabel: 99992, MPLSExp: 7}, + GREFlow: &otgconfighelpers.GREFlowParams{Protocol: otgconfighelpers.IanaMPLSEthertype}, + } + // FlowOuterIPv6Validation MPLSOGRE traffic IPv6 interface IPv6 Payload. + FlowOuterIPv6Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[1].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowOuterIPv6.FlowName, TolerancePct: 0.5}, + } + // FlowInnerIPv6 Inner Header IPv6 Payload. + FlowInnerIPv6 = &otgconfighelpers.Flow{ + IPv6Flow: &otgconfighelpers.IPv6FlowParams{IPv6Src: "2000:1::1", IPv6Dst: "3000:1::1", IPv6SrcCount: 10000, TrafficClass: 0, TrafficClassCount: 255}, + TCPFlow: &otgconfighelpers.TCPFlowParams{TCPSrcPort: 49152, TCPDstPort: 80, TCPSrcCount: 10000}, + } + validationsIPv4 = []packetvalidationhelpers.ValidationType{ + packetvalidationhelpers.ValidateIPv4Header, + packetvalidationhelpers.ValidateTCPHeader, + } + decapValidationIPv4 = &packetvalidationhelpers.PacketValidation{ + PortName: "port1", + CaptureName: "ipv4_decap", + Validations: validationsIPv4, + IPv4Layer: &packetvalidationhelpers.IPv4Layer{DstIP: "21.1.1.1", Tos: 10, TTL: 64, Protocol: packetvalidationhelpers.TCP}, + TCPLayer: &packetvalidationhelpers.TCPLayer{SrcPort: 49152, DstPort: 80}, + } +) + +func ConfigureOTG(t *testing.T) { + t.Helper() + top.Captures().Clear() + ate := ondatra.ATE(t, "ate") + + // Create a slice of aggPortData for easier iteration + aggs := []*otgconfighelpers.Port{agg1, agg2} + + // Configure OTG Interfaces + for _, agg := range aggs { + otgconfighelpers.ConfigureNetworkInterface(t, top, ate, agg) + } + ate.OTG().PushConfig(t, top) +} + +// PF-1.12.1: Generate DUT Configuration +func ConfigureDut(t *testing.T, dut *ondatra.DUTDevice, ocPFParams cfgplugins.OcPolicyForwardingParams) { + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, custPorts, []*attrs.Attributes{&custIntfIPv4, &custIntfIPv6, &custIntfdualStack, &custIntfIPv4MultiCloud, &custIntfIPv4JumboMTU}, aggID) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv6, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfdualStack, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4MultiCloud, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4JumboMTU, ocPFParams) + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, corePorts, []*attrs.Attributes{&coreIntf}, aggID) + configureStaticRoute(t, dut) + _, ni, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + DecapMPLSInGRE(t, dut, pf, ni, ocPFParams) + +} + +func TestSetup(t *testing.T) { + t.Log("PF-1.12.1: Generate DUT Configuration") + dut := ondatra.DUT(t, "dut") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + // Get default parameters for OC Policy Forwarding + ocPFParams := GetDefaultOcPolicyForwardingParams() + + // Pass ocPFParams to ConfigureDut + ConfigureDut(t, dut, ocPFParams) + ConfigureOTG(t) +} + +// GetDefaultOcPolicyForwardingParams provides default parameters for the generator, +// matching the values in the provided JSON example. +func GetDefaultOcPolicyForwardingParams() cfgplugins.OcPolicyForwardingParams { + return cfgplugins.OcPolicyForwardingParams{ + NetworkInstanceName: "DEFAULT", + InterfaceID: "Agg1.10", + AppliedPolicyName: "customer1", + } +} + +func configureInterfaceProperties(t *testing.T, dut *ondatra.DUTDevice, aggID string, a *attrs.Attributes, ocPFParams cfgplugins.OcPolicyForwardingParams) { + _, _, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + + if a.IPv4 != "" { + cfgplugins.InterfacelocalProxyConfig(t, dut, a, aggID) + } + cfgplugins.InterfaceQosClassificationConfig(t, dut, a, aggID) + cfgplugins.InterfacePolicyForwardingConfig(t, dut, a, aggID, pf, ocPFParams) +} + +// function should also include the OC config , within these deviations there should be a switch statement is needed +// Modified to accept pf, ni, and ocPFParams +func DecapMPLSInGRE(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ni *oc.NetworkInstance, ocPFParams cfgplugins.OcPolicyForwardingParams) { + cfgplugins.MplsConfig(t, dut) + cfgplugins.QosClassificationConfig(t, dut) + cfgplugins.LabelRangeConfig(t, dut) + cfgplugins.DecapGroupConfigGre(t, dut, pf, ocPFParams) + cfgplugins.MPLSStaticLSPConfig(t, dut, ni, ocPFParams) + if !deviations.PolicyForwardingOCUnsupported(dut) { + PushPolicyForwardingConfig(t, dut, ni) + } +} + +// PF-1.12.2: Verify PF MPLSoGRE Decap action for IPv4 and IPv6 traffic. +func TestMPLSOGREDecapIPv4AndIPv6(t *testing.T) { + ate := ondatra.ATE(t, "ate") + t.Log("PF-1.12.2: Verify MPLSoGRE decapsulate action for IPv4 and IPv6 payload") + createflow(t, top, FlowOuterIPv4, FlowInnerIPv4, true) + createflow(t, top, FlowOuterIPv6, FlowInnerIPv6, false) + sendTraffic(t, ate) + if err := FlowOuterIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } + if err := FlowOuterIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } +} + +// PF-1.12.5: Verify MPLSoGRE DSCP/TTL preserve operation. +func TestMPLSOGREDecapInnerPayloadPreserve(t *testing.T) { + ate := ondatra.ATE(t, "ate") + t.Log("PF-1.12.5: Verify MPLSoGRE DSCP/TTL preserve operation") + packetvalidationhelpers.ClearCapture(t, top, ate) + updateFlow(t, FlowOuterIPv4, FlowInnerIPv4, true, 100, 1000) + packetvalidationhelpers.ConfigurePacketCapture(t, top, decapValidationIPv4) + sendTrafficCapture(t, ate) + if err := FlowOuterIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } + if err := packetvalidationhelpers.CaptureAndValidatePackets(t, ate, decapValidationIPv4); err != nil { + t.Errorf("CaptureAndValidatePackets(): got err: %q", err) + } +} + +// PF-1.12.6: Verify IPV4/IPV6 nexthop resolution of decap traffic +func TestMPLSOGREDecapNextHopResolution(t *testing.T) { + ate := ondatra.ATE(t, "ate") + totalPackets := uint32(100000) + pps := uint64(1000) + t.Log("PF-1.12.6: Verify IPV4/IPV6 nexthop resolution of decap traffic") + updateFlow(t, FlowOuterIPv4, FlowInnerIPv4, true, pps, totalPackets) + updateFlow(t, FlowOuterIPv6, FlowInnerIPv6, false, pps, totalPackets) + // Make sure the next hop resolution is done for IPv4 and IPv6 Interfaces facing towards customer Interfaces in OTG. + nextHopResolutionIPv4.IsIPv4Interfaceresolved(t, ate) + nextHopResolutionIPv6.IsIPv6Interfaceresolved(t, ate) + sendTraffic(t, ate) + if err := FlowOuterIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } + if err := FlowOuterIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } +} + +func sendTraffic(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + flowResolveArp.IsIPv4Interfaceresolved(t, ate) + ate.OTG().StartTraffic(t) + time.Sleep(120 * time.Second) + ate.OTG().StopTraffic(t) +} + +func sendTrafficCapture(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + flowResolveArp.IsIPv4Interfaceresolved(t, ate) + cs := packetvalidationhelpers.StartCapture(t, ate) + ate.OTG().StartTraffic(t) + time.Sleep(60 * time.Second) + ate.OTG().StopTraffic(t) + packetvalidationhelpers.StopCapture(t, ate, cs) +} + +func createflow(t *testing.T, top gosnappi.Config, paramsOuter *otgconfighelpers.Flow, paramsInner *otgconfighelpers.Flow, clearFlows bool) { + if clearFlows { + top.Flows().Clear() + } + paramsOuter.CreateFlow(top) + paramsOuter.AddEthHeader() + paramsOuter.AddIPv4Header() + paramsOuter.AddGREHeader() + paramsOuter.AddMPLSHeader() + if paramsInner.IPv4Flow != nil { + *paramsOuter.IPv4Flow = *paramsInner.IPv4Flow + paramsOuter.AddIPv4Header() + } + if paramsInner.IPv6Flow != nil { + paramsOuter.IPv6Flow = paramsInner.IPv6Flow + paramsOuter.AddIPv6Header() + } + if paramsInner.TCPFlow != nil { + paramsOuter.TCPFlow = paramsInner.TCPFlow + paramsOuter.AddTCPHeader() + } + if paramsInner.UDPFlow != nil { + paramsOuter.UDPFlow = paramsInner.UDPFlow + paramsOuter.AddUDPHeader() + } +} + +func updateFlow(t *testing.T, paramsOuter *otgconfighelpers.Flow, paramsInner *otgconfighelpers.Flow, clearFlows bool, pps uint64, totalPackets uint32) { + paramsOuter.PacketsToSend = totalPackets + paramsOuter.PpsRate = pps + paramsOuter.Flowrate = 0 + if paramsInner.IPv6Flow != nil { + paramsInner.IPv6Flow.TrafficClassCount = 0 + paramsInner.IPv6Flow.TrafficClass = 10 + } + if paramsInner.IPv4Flow != nil { + paramsInner.IPv4Flow.RawPriorityCount = 0 + paramsInner.IPv4Flow.RawPriority = 10 + if paramsInner.TCPFlow != nil { + paramsInner.TCPFlow.TCPSrcCount = 0 + paramsInner.TCPFlow.TCPSrcPort = 49152 + } + paramsOuter.IPv4Flow.IPv4Src = "100.64.0.1" + paramsOuter.IPv4Flow.IPv4Dst = "11.1.1.1" + } + createflow(t, top, paramsOuter, paramsInner, clearFlows) +} + +func configureInterfaces(t *testing.T, dut *ondatra.DUTDevice, dutPorts []string, subinterfaces []*attrs.Attributes, aggID string) { + t.Helper() + d := gnmi.OC() + dutAggPorts := []*ondatra.Port{} + for _, port := range dutPorts { + dutAggPorts = append(dutAggPorts, dut.Port(t, port)) + } + if deviations.AggregateAtomicUpdate(dut) { + cfgplugins.DeleteAggregate(t, dut, aggID, dutAggPorts) + cfgplugins.SetupAggregateAtomically(t, dut, aggID, dutAggPorts) + } + + lacp := &oc.Lacp_Interface{Name: ygot.String(aggID)} + lacp.LacpMode = oc.Lacp_LacpActivityType_ACTIVE + lacpPath := d.Lacp().Interface(aggID) + fptest.LogQuery(t, "LACP", lacpPath.Config(), lacp) + gnmi.Replace(t, dut, lacpPath.Config(), lacp) + // TODO - to remove this sleep later + time.Sleep(5 * time.Second) + + agg := &oc.Interface{Name: ygot.String(aggID)} + configDUTInterface(agg, subinterfaces, dut) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_LACP + agg.Type = ieee8023adLag + aggPath := d.Interface(aggID) + fptest.LogQuery(t, aggID, aggPath.Config(), agg) + gnmi.Replace(t, dut, aggPath.Config(), agg) + + for _, port := range dutAggPorts { + holdTimeConfig := &oc.Interface_HoldTime{ + Up: ygot.Uint32(3000), + Down: ygot.Uint32(150), + } + intfPath := gnmi.OC().Interface(port.Name()) + gnmi.Update(t, dut, intfPath.HoldTime().Config(), holdTimeConfig) + } +} +func configDUTInterface(i *oc.Interface, subinterfaces []*attrs.Attributes, dut *ondatra.DUTDevice) { + for _, a := range subinterfaces { + i.Description = ygot.String(a.Desc) + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s1 := i.GetOrCreateSubinterface(0) + b4 := s1.GetOrCreateIpv4() + b6 := s1.GetOrCreateIpv6() + b4.Mtu = ygot.Uint16(a.MTU) + b6.Mtu = ygot.Uint32(uint32(a.MTU)) + if deviations.InterfaceEnabled(dut) { + b4.Enabled = ygot.Bool(true) + } + if a.Subinterface != 0 { + s := i.GetOrCreateSubinterface(a.Subinterface) + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(uint16(a.Subinterface)) + configureInterfaceAddress(dut, s, a) + } else { + configureInterfaceAddress(dut, s1, a) + } + } +} +func configureInterfaceAddress(dut *ondatra.DUTDevice, s *oc.Interface_Subinterface, a *attrs.Attributes) { + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + if a.IPv4 != "" { + a4 := s4.GetOrCreateAddress(a.IPv4) + a4.PrefixLength = ygot.Uint8(a.IPv4Len) + } + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + if a.IPv6 != "" { + s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(a.IPv6Len) + } + + if a.IPv6Sec != "" { + s6_2 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6_2.Enabled = ygot.Bool(true) + } + s6_2.GetOrCreateAddress(a.IPv6Sec).PrefixLength = ygot.Uint8(a.IPv6Len) + } +} + +func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + b := &gnmi.SetBatch{} + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: "10.99.1.0/24", + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString("194.0.2.2"), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) +} + +func PushPolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance) { + t.Helper() + niPath := gnmi.OC().NetworkInstance(ni.GetName()).Config() + gnmi.Replace(t, dut, niPath, ni) +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/README.md index 1c25957a125..07258977942 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/README.md @@ -1,4 +1,4 @@ -# PF-1.15 - MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload scale test +# PF-1.15: MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload scale test ## Summary @@ -42,6 +42,7 @@ Verify: * Verify that device can achieve the maximum interface scale on the device * Verify that entire static label range is usable and functional by sending traffic across the entire label range + ## OpenConfig Path and RPC Coverage TODO: Finalize and update the below paths after the review and testing on any vendor device. @@ -91,4 +92,4 @@ rpcs: ## Minimum DUT platform requirement -FFF \ No newline at end of file +FFF diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/metadata.textproto new file mode 100644 index 00000000000..fe800a432a3 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_scale_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "96cd04f1-4054-4429-90e7-6a04b8e3722a" +plan_id: "PF-1.15" +description: "MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload scale test" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md index 4f222bc2d73..6501e81faa8 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md @@ -1,4 +1,4 @@ -# PF-1.14 - MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload +# PF-1.14: MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload ## Summary diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/metadata.textproto new file mode 100644 index 00000000000..fdb39612651 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/metadata.textproto @@ -0,0 +1,36 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c46a4b9b-5c89-4fd4-897b-99f311ca84e1" +plan_id: "PF-1.14" +description: "MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + aggregate_atomic_update: true + interface_enabled: true + default_network_instance: "default" + next_hop_group_config_unsupported: true + mpls_unsupported: true + mpls_label_classification_unsupported: true + local_proxy_unsupported: true + qos_classification_unsupported: true + policy_forwarding_unsupported: true + interface_policy_forwarding_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + interface_ref_config_unsupported: true + rib_wecmp: true + wecmp_auto_unsupported: true + isis_loopback_required: true + weighted_ecmp_fixed_packet_verification: true + } +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/mpls_gre_ipv4_encap_test.go b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/mpls_gre_ipv4_encap_test.go new file mode 100644 index 00000000000..5aaf75a7a0d --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/mpls_gre_ipv4_encap_test.go @@ -0,0 +1,698 @@ +// Package mpls_gre_ipv4_encap_test tests mplsogre encap functionality. +package mpls_gre_ipv4_encap_test + +import ( + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + otgconfighelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_config_helpers" + otgvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_validation_helpers" + packetvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/packetvalidationhelpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygot/ygot" +) + +// TestMain calls main function. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag + GREProtocol = 47 +) + +var ( + top = gosnappi.NewConfig() + aggID string + custPorts = []string{"port1", "port2"} + corePorts = []string{"port3", "port4"} + custIntfIPv4 = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 1500, + IPv4: "169.254.0.11", + IPv4Len: 29, + Subinterface: 20, + } + custIntfIPv6 = attrs.Attributes{ + Desc: "Customer_connectv6", + MTU: 1500, + IPv6: "2600:2d00:0:1:8000:10:0:ca31", + IPv6Sec: "2600:2d00:0:1:8000:10:0:ca33", + IPv6Len: 125, + Subinterface: 21, + } + + custIntfdualStack = attrs.Attributes{ + Desc: "Customer_connect_dualstack", + MTU: 1500, + IPv4: "169.254.0.27", + IPv4Len: 29, + IPv6: "2600:2d00:0:1:7000:10:0:ca31", + IPv6Sec: "2600:2d00:0:1:7000:10:0:ca33", + IPv6Len: 125, + Subinterface: 22, + } + custIntfIPv4MultiCloud = attrs.Attributes{ + Desc: "Customer_connect_multicloud", + MTU: 1500, + IPv4: "169.254.0.33", + IPv4Len: 30, + Subinterface: 23, + } + custIntfIPv4JumboMTU = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 9066, + IPv4: "169.254.0.53", + IPv4Len: 29, + Subinterface: 26, + } + coreIntf = attrs.Attributes{ + Desc: "Core_Interface", + IPv4: "194.0.2.1", + IPv6: "2001:10:1:6::1", + MTU: 9202, + IPv4Len: 24, + IPv6Len: 126, + } + + agg1 = &otgconfighelpers.Port{ + Name: "Port-Channel1", + AggMAC: "02:00:01:01:01:07", + Interfaces: []*otgconfighelpers.InterfaceProperties{interface1, interface2, interface3, interface4, interface8}, + MemberPorts: []string{"port1", "port2"}, + LagID: 1, + IsLag: true, + } + agg2 = &otgconfighelpers.Port{ + Name: "Port-Channel2", + AggMAC: "02:00:01:01:01:01", + MemberPorts: []string{"port3", "port4"}, + Interfaces: []*otgconfighelpers.InterfaceProperties{interface7}, + LagID: 2, + IsLag: true, + } + + interface1 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.12", + IPv4Gateway: "169.254.0.11", + Name: "Port-Channel1.20", + MAC: "02:00:01:01:01:08", + Vlan: 20, + IPv4Len: 29, + } + interface2 = &otgconfighelpers.InterfaceProperties{ + IPv6: "2600:2d00:0:1:8000:10:0:ca32", + IPv6Gateway: "2600:2d00:0:1:8000:10:0:ca31", + MAC: "02:00:01:01:01:09", + Name: "Port-Channel1.21", + Vlan: 21, + IPv6Len: 125, + } + interface3 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.26", + IPv4Gateway: "169.254.0.27", + IPv6: "2600:2d00:0:1:7000:10:0:ca32", + IPv6Gateway: "2600:2d00:0:1:7000:10:0:ca31", + MAC: "02:00:01:01:01:10", + Name: "Port-Channel1.22", + Vlan: 22, + IPv4Len: 29, + IPv6Len: 125, + } + interface4 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.34", + IPv4Gateway: "169.254.0.33", + Name: "Port-Channel1.23", + MAC: "02:00:01:01:01:11", + Vlan: 23, + IPv4Len: 30, + } + interface8 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.54", + IPv4Gateway: "169.254.0.53", + Name: "Port-Channel1.26", + MAC: "02:00:01:01:01:13", + Vlan: 26, + IPv4Len: 29, + } + interface7 = &otgconfighelpers.InterfaceProperties{ + IPv4: "194.0.2.2", + IPv6: "2001:10:1:6::2", + IPv4Gateway: "194.0.2.1", + IPv6Gateway: "2001:10:1:6::1", + Name: "Port-Channel2", + MAC: "02:00:01:01:01:02", + IPv4Len: 29, + IPv6Len: 126, + } + // Custom IMIX settings for all flows. + sizeWeightProfile = []otgconfighelpers.SizeWeightPair{ + {Size: 64, Weight: 20}, + {Size: 128, Weight: 20}, + {Size: 256, Weight: 20}, + {Size: 512, Weight: 10}, + {Size: 1500, Weight: 28}, + {Size: 9000, Weight: 2}, + } + // MPLSOGRE Encap IPv4 interface IPv4 Payload + FlowIPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[0].Name + ".IPv4"}, + RxNames: []string{agg2.Name + ".IPv4"}, + SizeWeightProfile: &sizeWeightProfile, + Flowrate: 80, + FlowName: "GCI traffic IPv4 interface IPv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 20}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 100, RawPriority: 0, RawPriorityCount: 100}, + } + + FlowIPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[0].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowIPv4.FlowName, TolerancePct: 0.5}, + } + // FlowMultiCloudIPv4 IPv4 Flow configuration. + FlowMultiCloudIPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[3].Name + ".IPv4"}, + RxNames: []string{agg2.Name + ".IPv4"}, + SizeWeightProfile: &sizeWeightProfile, + FlowName: "GCI traffic IPv4 interface IPv4 MultiCloud Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 23}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4DstCount: 10, DSCP: 0, DSCPCount: 63}, + } + + // FlowMultiCloudIPv4Validation Encap IPv4 flow validation. + FlowMultiCloudIPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[3].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowMultiCloudIPv4.FlowName, TolerancePct: 0.5}, + } + // MPLSOGRE Encap IPv6 interface IPv6 Payload + FlowIPv6 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[1].Name + ".IPv6"}, + RxNames: []string{agg2.Name + ".IPv6"}, + FrameSize: 1500, + Flowrate: 80, + FlowName: "GCI traffic IPv6 interface IPv6 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 21}, + IPv6Flow: &otgconfighelpers.IPv6FlowParams{IPv6Src: "3000:1::1", IPv6Dst: "2000:1::1", IPv6SrcCount: 100, TrafficClass: 0, TrafficClassCount: 100}, + } + + FlowIPv6Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[1].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowIPv6.FlowName, TolerancePct: 0.5}, + } + // MPLSOGRE Encap IPv4+IPv6 Interface IPv4 Payload + FlowDualIPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[2].Name + ".IPv4"}, + RxNames: []string{agg2.Name + ".IPv4"}, + FrameSize: 1500, + FlowName: "GCI traffic IPv4+IPv6 interface IPv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 22}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 100, RawPriority: 0, RawPriorityCount: 100}, + } + + FlowDualIPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[2].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowDualIPv4.FlowName, TolerancePct: 0.5}, + } + + // MPLSOGRE Encap IPv4+IPv6 Interface IPv6 Payload + FlowDualIPv6 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[2].Name + ".IPv6"}, + RxNames: []string{agg2.Name + ".IPv6"}, + FrameSize: 1500, + FlowName: "GCI traffic IPv4+IPv6 interface IPv6 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 22}, + IPv6Flow: &otgconfighelpers.IPv6FlowParams{IPv6Src: "3000:1::1", IPv6Dst: "2000:1::1", IPv6SrcCount: 100, TrafficClass: 0, TrafficClassCount: 100}, + } + + FlowDualIPv6Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[2].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowDualIPv6.FlowName, TolerancePct: 0.5}, + } + // MPLSOGRE Encap IPv4 Interface BGPv4 Payload + flowBGPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[0].Name + ".IPv4"}, + RxNames: []string{agg2.Name + ".IPv4"}, + FrameSize: 1500, + FlowName: "GCI traffic IPv4 interface, BGPv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 20}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 100, RawPriority: 0, RawPriorityCount: 100}, + TCPFlow: &otgconfighelpers.TCPFlowParams{TCPSrcPort: 49152, TCPDstPort: 179}, + } + + flowBGPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[0].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: flowBGPv4.FlowName, TolerancePct: 0.5}, + } + + // MPLSOGRE Encap IPv4 Interface BFDv4 Payload + flowBFDv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[0].Name + ".IPv4"}, + RxNames: []string{agg2.Name + ".IPv4"}, + FrameSize: 1500, + FlowName: "GCI traffic IPv4 interface, BFDv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 20}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 100, RawPriority: 0, RawPriorityCount: 100}, + UDPFlow: &otgconfighelpers.UDPFlowParams{UDPSrcPort: 49152, UDPDstPort: 3784}, + } + + flowBFDv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[0].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: flowBFDv4.FlowName, TolerancePct: 0.5}, + } + Validations = []packetvalidationhelpers.ValidationType{ + packetvalidationhelpers.ValidateIPv4Header, + packetvalidationhelpers.ValidateMPLSLayer, + packetvalidationhelpers.ValidateInnerIPv4Header, + } + OuterGREIPLayerIPv4 = &packetvalidationhelpers.IPv4Layer{ + Protocol: GREProtocol, + DstIP: "10.99.1.1", + Tos: 96, + TTL: 64, + } + MPLSLayer = &packetvalidationhelpers.MPLSLayer{ + Label: 116383, + Tc: 1, + } + InnerIPLayerIPv4 = &packetvalidationhelpers.IPv4Layer{ + DstIP: "11.1.1.1", + Tos: 1, + TTL: 63, + } + InnerIPLayerIPv6 = &packetvalidationhelpers.IPv6Layer{ + DstIP: "2000:1::1", + TrafficClass: 10, + HopLimit: 63, + } + EncapPacketValidation = &packetvalidationhelpers.PacketValidation{ + PortName: "port3", + IPv4Layer: OuterGREIPLayerIPv4, + MPLSLayer: MPLSLayer, + Validations: Validations, + InnerIPLayerIPv4: InnerIPLayerIPv4, + InnerIPLayerIPv6: InnerIPLayerIPv6, + TCPLayer: &packetvalidationhelpers.TCPLayer{SrcPort: 49152, DstPort: 179}, + UDPLayer: &packetvalidationhelpers.UDPLayer{SrcPort: 49152, DstPort: 3784}, + } +) + +func ConfigureOTG(t *testing.T) { + t.Helper() + top.Captures().Clear() + ate := ondatra.ATE(t, "ate") + + // Create a slice of aggPortData for easier iteration + aggs := []*otgconfighelpers.Port{agg1, agg2} + + // Configure OTG Interfaces + for _, agg := range aggs { + otgconfighelpers.ConfigureNetworkInterface(t, top, ate, agg) + } + ate.OTG().PushConfig(t, top) +} + +// PF-1.14.1: Generate DUT Configuration +// Modified to create and pass OC root, ni, pf +func ConfigureDut(t *testing.T, dut *ondatra.DUTDevice, ocPFParams cfgplugins.OcPolicyForwardingParams, ocNHGParams cfgplugins.StaticNextHopGroupParams) { + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, custPorts, []*attrs.Attributes{&custIntfIPv4, &custIntfIPv6, &custIntfdualStack, &custIntfIPv4MultiCloud, &custIntfIPv4JumboMTU}, aggID) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv6, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfdualStack, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4MultiCloud, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4JumboMTU, ocPFParams) + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, corePorts, []*attrs.Attributes{&coreIntf}, aggID) + configureStaticRoute(t, dut) + _, ni, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + EncapMPLSInGRE(t, dut, pf, ni, ocPFParams, ocNHGParams) + +} + +func TestSetup(t *testing.T) { + t.Log("PF-1.14.1: Generate DUT Configuration") + dut := ondatra.DUT(t, "dut") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + // Get default parameters for OC Policy Forwarding + ocPFParams := GetDefaultOcPolicyForwardingParams() + ocNHGParams := GetDefaultStaticNextHopGroupParams() + + // Pass ocPFParams to ConfigureDut + ConfigureDut(t, dut, ocPFParams, ocNHGParams) + ConfigureOTG(t) +} + +// GetDefaultStaticNextHopGroupParams provides default parameters for the generator. +// matching the values in the provided JSON example. +func GetDefaultStaticNextHopGroupParams() cfgplugins.StaticNextHopGroupParams { + return cfgplugins.StaticNextHopGroupParams{ + + StaticNHGName: "MPLS_in_GRE_Encap", + NHIPAddr1: "nh_ip_addr_1", + NHIPAddr2: "nh_ip_addr_2", + // TODO: b/417988636 - Set the MplsLabel to the correct value. + } +} + +// GetDefaultOcPolicyForwardingParams provides default parameters for the generator, +// matching the values in the provided JSON example. +func GetDefaultOcPolicyForwardingParams() cfgplugins.OcPolicyForwardingParams { + return cfgplugins.OcPolicyForwardingParams{ + NetworkInstanceName: "DEFAULT", + InterfaceID: "Agg1.10", + AppliedPolicyName: "customer1", + } +} + +func configureInterfaceProperties(t *testing.T, dut *ondatra.DUTDevice, aggID string, a *attrs.Attributes, ocPFParams cfgplugins.OcPolicyForwardingParams) { + _, _, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + + if a.IPv4 != "" { + cfgplugins.InterfacelocalProxyConfig(t, dut, a, aggID) + } + cfgplugins.InterfaceQosClassificationConfig(t, dut, a, aggID) + cfgplugins.InterfacePolicyForwardingConfig(t, dut, a, aggID, pf, ocPFParams) +} + +// function should also include the OC config , within these deviations there should be a switch statement is needed +// Modified to accept pf, ni, and ocPFParams +func EncapMPLSInGRE(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ni *oc.NetworkInstance, ocPFParams cfgplugins.OcPolicyForwardingParams, ocNHGParams cfgplugins.StaticNextHopGroupParams) { + cfgplugins.MplsConfig(t, dut) + cfgplugins.QosClassificationConfig(t, dut) + cfgplugins.LabelRangeConfig(t, dut) + cfgplugins.NextHopGroupConfig(t, dut, "v4", ni, ocNHGParams) + cfgplugins.PolicyForwardingConfig(t, dut, "v4", pf, ocPFParams) + cfgplugins.NextHopGroupConfig(t, dut, "v6", ni, ocNHGParams) + cfgplugins.PolicyForwardingConfig(t, dut, "v6", pf, ocPFParams) + cfgplugins.NextHopGroupConfig(t, dut, "dualstack", ni, ocNHGParams) + cfgplugins.PolicyForwardingConfig(t, dut, "dualstack", pf, ocPFParams) + cfgplugins.NextHopGroupConfig(t, dut, "multicloudv4", ni, ocNHGParams) + cfgplugins.PolicyForwardingConfig(t, dut, "multicloudv4", pf, ocPFParams) + if !deviations.PolicyForwardingOCUnsupported(dut) { + PushPolicyForwardingConfig(t, dut, ni) + } +} + +// TestMPLSOGREEncapIPv4 tests MPLSOGRE Encap test case for IPv4 flow. +func TestMPLSOGREEncapIPv4(t *testing.T) { + ate := ondatra.ATE(t, "ate") + t.Log("PF-1.14.2: Verify PF MPLSoGRE encapsulate action for IPv4 traffic") + createflow(t, top, FlowIPv4, true) + sendTraffic(t, ate) + + if err := FlowIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("Validation on flows failed (): %q", err) + } + FlowIPv4.IPv4Flow.RawPriority = 1 + FlowIPv4.IPv4Flow.RawPriorityCount = 0 + FlowIPv4.PacketsToSend = 1000 + + createflow(t, top, FlowIPv4, true) + packetvalidationhelpers.ConfigurePacketCapture(t, top, EncapPacketValidation) + sendTrafficCapture(t, ate) + if err := FlowIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + packetvalidationhelpers.ClearCapture(t, top, ate) + t.Errorf("Validation on flows failed (): %q", err) + } + + if err := packetvalidationhelpers.CaptureAndValidatePackets(t, ate, EncapPacketValidation); err != nil { + packetvalidationhelpers.ClearCapture(t, top, ate) + t.Errorf("Capture And ValidatePackets Failed (): %q", err) + } + packetvalidationhelpers.ClearCapture(t, top, ate) +} + +func TestMPLSOGREEncapIPv4Ttl(t *testing.T) { + t.Log("PF-1.14.7: Verify IPV4/IPV6 selective local traffic processing") + ate := ondatra.ATE(t, "ate") + + FlowIPv4.IPv4Flow.TTL = 1 + createflow(t, top, FlowIPv4, true) + sendTraffic(t, ate) + if err := FlowIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): %q", err) + } +} + +// TestMPLSOGREEncapIPv6 tests mplsogre encap functionality for IPv6 traffic. +func TestMPLSOGREEncapIPv6(t *testing.T) { + t.Log("PF-1.14.3: Verify PF MPLSoGRE encapsulate action for IPv6 traffic") + ate := ondatra.ATE(t, "ate") + + createflow(t, top, FlowIPv6, true) + sendTraffic(t, ate) + if err := FlowIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): %q", err) + } + + FlowIPv6.IPv6Flow.TrafficClass = 10 + FlowIPv6.IPv6Flow.TrafficClassCount = 0 + FlowIPv6.PacketsToSend = 1000 + createflow(t, top, FlowIPv6, true) + packetvalidationhelpers.ConfigurePacketCapture(t, top, EncapPacketValidation) + sendTrafficCapture(t, ate) + if err := FlowIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + packetvalidationhelpers.ClearCapture(t, top, ate) + t.Errorf("ValidateLossOnFlows(): %q", err) + } + EncapPacketValidation.IPv4Layer.DstIP = "10.99.1.2" + EncapPacketValidation.MPLSLayer.Label = 99999 + EncapPacketValidation.Validations = []packetvalidationhelpers.ValidationType{ + packetvalidationhelpers.ValidateIPv4Header, + packetvalidationhelpers.ValidateMPLSLayer, + packetvalidationhelpers.ValidateInnerIPv6Header, + } + + if err := packetvalidationhelpers.CaptureAndValidatePackets(t, ate, EncapPacketValidation); err != nil { + packetvalidationhelpers.ClearCapture(t, top, ate) + t.Errorf("CaptureAndValidatePackets(): %q", err) + } + packetvalidationhelpers.ClearCapture(t, top, ate) +} +func TestMPLSOGREEncapIPv6Ttl(t *testing.T) { + ate := ondatra.ATE(t, "ate") + // OtgPreValidation(t, FlowIPv6Validation, "IPv6") + FlowIPv6.IPv6Flow.HopLimit = 1 + createflow(t, top, FlowIPv6, true) + sendTraffic(t, ate) + if err := FlowIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): %q", err) + } +} + +func TestMPLSOGREEncapDualStack(t *testing.T) { + t.Log("PF-1.14.4: Verify PF MPLSoGRE encapsulate action for IPv6 traffic") + ate := ondatra.ATE(t, "ate") + createflow(t, top, FlowDualIPv4, true) + createflow(t, top, FlowDualIPv6, false) + sendTraffic(t, ate) + if err := FlowDualIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): %q", err) + } + if err := FlowDualIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): %q", err) + } + +} +func TestMPLSOGREEncapBGPv4(t *testing.T) { + t.Log("PF-1.14.7: Verify IPV4/IPV6 selective local traffic processing") + ate := ondatra.ATE(t, "ate") + createflow(t, top, flowBGPv4, true) + sendTraffic(t, ate) + if err := flowBGPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q", err) + } +} + +func TestMPLSOGREEncapBFDv4(t *testing.T) { + t.Log("PF-1.14.7: Verify IPV4/IPV6 selective local traffic processing") + ate := ondatra.ATE(t, "ate") + createflow(t, top, flowBFDv4, true) + sendTraffic(t, ate) + if err := flowBFDv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q", err) + } +} + +// OTGPreValidation validates the OTG port status and interface resolution. +func OTGPreValidation(t *testing.T, params *otgvalidationhelpers.OTGValidation, interfaceType string) { + ate := ondatra.ATE(t, "ate") + if err := params.ValidatePortIsActive(t, ate); err != nil { + t.Errorf("ValidatePortIsActive(): %q", err) + } + if interfaceType == "IPv4" { + if err := params.IsIPv4Interfaceresolved(t, ate); err != nil { + t.Errorf("IsIPv4Interfaceresolved(): %q", err) + } + } + if interfaceType == "IPv6" { + if err := params.IsIPv6Interfaceresolved(t, ate); err != nil { + t.Errorf("IsIPv6Interfaceresolved(): %q", err) + } + } +} + +func sendTraffic(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + ate.OTG().StartTraffic(t) + time.Sleep(120 * time.Second) + ate.OTG().StopTraffic(t) +} + +func createflow(t *testing.T, top gosnappi.Config, params *otgconfighelpers.Flow, clearFlows bool) { + if clearFlows { + top.Flows().Clear() + } + params.CreateFlow(top) + params.AddEthHeader() + params.AddVLANHeader() + if params.IPv4Flow != nil { + params.AddIPv4Header() + } + if params.IPv6Flow != nil { + params.AddIPv6Header() + } + if params.TCPFlow != nil { + params.AddTCPHeader() + } + if params.UDPFlow != nil { + params.AddUDPHeader() + } +} + +func configureInterfaces(t *testing.T, dut *ondatra.DUTDevice, dutPorts []string, subinterfaces []*attrs.Attributes, aggID string) { + t.Helper() + d := gnmi.OC() + dutAggPorts := []*ondatra.Port{} + for _, port := range dutPorts { + dutAggPorts = append(dutAggPorts, dut.Port(t, port)) + } + if deviations.AggregateAtomicUpdate(dut) { + cfgplugins.DeleteAggregate(t, dut, aggID, dutAggPorts) + cfgplugins.SetupAggregateAtomically(t, dut, aggID, dutAggPorts) + } + + lacp := &oc.Lacp_Interface{Name: ygot.String(aggID)} + lacp.LacpMode = oc.Lacp_LacpActivityType_ACTIVE + lacpPath := d.Lacp().Interface(aggID) + fptest.LogQuery(t, "LACP", lacpPath.Config(), lacp) + gnmi.Replace(t, dut, lacpPath.Config(), lacp) + // TODO - to remove this sleep later + time.Sleep(5 * time.Second) + + agg := &oc.Interface{Name: ygot.String(aggID)} + configDUTInterface(agg, subinterfaces, dut) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_LACP + agg.Type = ieee8023adLag + aggPath := d.Interface(aggID) + fptest.LogQuery(t, aggID, aggPath.Config(), agg) + gnmi.Replace(t, dut, aggPath.Config(), agg) + + for _, port := range dutAggPorts { + holdTimeConfig := &oc.Interface_HoldTime{ + Up: ygot.Uint32(3000), + Down: ygot.Uint32(150), + } + intfPath := gnmi.OC().Interface(port.Name()) + gnmi.Update(t, dut, intfPath.HoldTime().Config(), holdTimeConfig) + } +} +func configDUTInterface(i *oc.Interface, subinterfaces []*attrs.Attributes, dut *ondatra.DUTDevice) { + for _, a := range subinterfaces { + i.Description = ygot.String(a.Desc) + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s1 := i.GetOrCreateSubinterface(0) + b4 := s1.GetOrCreateIpv4() + b6 := s1.GetOrCreateIpv6() + b4.Mtu = ygot.Uint16(a.MTU) + b6.Mtu = ygot.Uint32(uint32(a.MTU)) + if deviations.InterfaceEnabled(dut) { + b4.Enabled = ygot.Bool(true) + } + if a.Subinterface != 0 { + s := i.GetOrCreateSubinterface(a.Subinterface) + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(uint16(a.Subinterface)) + configureInterfaceAddress(dut, s, a) + } else { + configureInterfaceAddress(dut, s1, a) + } + } +} +func configureInterfaceAddress(dut *ondatra.DUTDevice, s *oc.Interface_Subinterface, a *attrs.Attributes) { + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + if a.IPv4 != "" { + a4 := s4.GetOrCreateAddress(a.IPv4) + a4.PrefixLength = ygot.Uint8(a.IPv4Len) + } + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + if a.IPv6 != "" { + s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(a.IPv6Len) + } + + if a.IPv6Sec != "" { + s6_2 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6_2.Enabled = ygot.Bool(true) + } + s6_2.GetOrCreateAddress(a.IPv6Sec).PrefixLength = ygot.Uint8(a.IPv6Len) + } +} + +func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + b := &gnmi.SetBatch{} + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: "10.99.1.0/24", + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString("194.0.2.2"), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) +} + +func sendTrafficCapture(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + cs := packetvalidationhelpers.StartCapture(t, ate) + ate.OTG().StartTraffic(t) + time.Sleep(60 * time.Second) + ate.OTG().StopTraffic(t) + time.Sleep(60 * time.Second) + packetvalidationhelpers.StopCapture(t, ate, cs) +} + +func PushPolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance) { + t.Helper() + niPath := gnmi.OC().NetworkInstance(ni.GetName()).Config() + gnmi.Replace(t, dut, niPath, ni) +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/README.md index 79558a21af8..93c86eb8b32 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/README.md @@ -1,4 +1,4 @@ -# PF-1.16 - MPLSoGRE IPV4 encapsulation IPV4/IPV6 local proxy test +# PF-1.16: MPLSoGRE IPV4 encapsulation IPV4/IPV6 local proxy test ## Summary @@ -187,4 +187,4 @@ rpcs: ## Minimum DUT platform requirement -FFF \ No newline at end of file +FFF diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/metadata.textproto new file mode 100644 index 00000000000..b409dbb1984 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_local_proxy_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "feae3402-857a-4a5c-8cbf-b8a5a16c0861" +plan_id: "PF-1.16" +description: "MPLSoGRE IPV4 encapsulation IPV4/IPV6 local proxy test" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/README.md index ebc79da1ce2..6a3a69c632d 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/README.md @@ -1,4 +1,4 @@ -# PF-1.7 Decapsulate MPLS in GRE and UDP +# PF-1.7: Decapsulate MPLS in GRE and UDP Create a policy-forwarding configuration using gNMI to decapsulate MPLS in GRE and UDP packets which are sent to a IP from a decap pool or loopback address and apply to diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/metadata.textproto new file mode 100644 index 00000000000..6b74b3a9308 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_decap_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "98b4065b-4af3-4217-a86c-f7b62a0cec8b" +plan_id: "PF-1.7" +description: "Decapsulate MPLS in GRE and UDP" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/README.md index c9e19c42d03..ed79051e78f 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/README.md @@ -1,4 +1,4 @@ -# PF-1.17 - MPLSoGRE and MPLSoGUE MACsec +# PF-1.17: MPLSoGRE and MPLSoGUE MACsec ## Summary This test verifies MACSec with MPLSoGRE and MPLSoGUE IP encap and decap traffic on the test device. @@ -90,6 +90,7 @@ Verify: * No packet loss when keys one through five expires as configured * 100 percent packet loss after all the keys configured expires + ## Canonical OpenConfig for MACsec configuration ```json diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/metadata.textproto new file mode 100644 index 00000000000..f3fab283dc8 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_macsec/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "73f52817-2667-44c8-837e-cb1a2769b599" +plan_id: "PF-1.17" +description: "MPLSoGRE and MPLSoGUE MACsec" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/README.md b/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/README.md index 45eae625e38..50145dff211 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/README.md @@ -1,4 +1,4 @@ -# PF-1.18 - MPLSoGRE and MPLSoGUE QoS +# PF-1.18: MPLSoGRE and MPLSoGUE QoS ## Summary This test verifies quality of service with MPLSoGRE and MPLSoGUE IP traffic on routed VLAN sub interfaces. The classification, marking and queueing of traffic while being encapsulated and decapsulated based on the outer headers and/or inner payload are the major features verified on the test device. @@ -195,116 +195,63 @@ Verify results are same corresponding to test cases PF-1.18.1 - PF-1.18.9 with: * Ingress and egress aggregate links on same PPE * Ingress links on multiple PPEs and egress aggregate links on multiple PPEs -## Canonical OpenConfig for MACsec configuration - -TODO: -* Finalize and update the below paths after the review and testing on any vendor device -* MPLSoGRE/MPLSoGUE packet classification OC need to be defined -* OC for Queueing with shaper need to be defined -* Add json queueing configs in this file - b/408493684 - -### JSON Format +#### Canonical OC NOTE: Multicast traffic must be sent out with L2 multicast header based on IP Multicast address even though there is no PIM on the egress interface ```json { - "network-instances": { - "network-instance": { - "DEFAULT": { - "mpls": { - "global": { - "interface-attributes": { - "interface": [ - { - "config": { - "interface-id": "Aggregate4", - "mpls-enabled": false - }, - "interface-id": "Aggregate4" - } - ] - } - }, - "lsps": { - "static-lsps": { - "static-lsp": [ - { - "config": { - "name": "Customer IPV4 in:40571 out:pop" - }, - "egress": { - "config": { - "incoming-label": 40571, - "next-hop": "169.254.1.138", - "pipe-mode": true # TODO: Add to OC data models following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 - } - } - }, - { - "config": { - "name": "Customer IPV6 in:40572 out:pop" - }, - "egress": { - "config": { - "incoming-label": 40572, - "next-hop": "2600:2d00:0:1:4000:15:69:2072", - "pipe-mode": true # TODO: Add to OC data models following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 - } - } - }, - { - "config": { - "name": "Customer multicast in:40573 out:pop" - }, - "egress": { - "config": { - "incoming-label": 40573, - "next-hop": "239.0.1.1", - "pipe-mode": true # TODO: Add to OC data models following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 - } - } - } - ] - } + "network-instances": { + "network-instance": [ + { + "config": { + "name": "default" + }, + "mpls": { + "lsps": { + "static-lsps": { + "static-lsp": [ + { + "config": { + "name": "Customer IPV4 in:40571 out:pop" + }, + "egress": { + "config": { + "incoming-label": 40571, + "next-hop": "169.254.1.138" } + }, + "name": "Customer IPV4 in:40571 out:pop" }, - "name": "default", - "policy-forwarding": { - "policies": { - "policy": [ - { - "config": { - "policy-id": "decap MPLS in GRE" - }, - "rules": { - "rule": [ - { - "action": { - "decapsulate-mpls-in-udp": true - }, - "config": { - "sequence-id": 1 - }, - "ipv4": { - "config": { - "destination-address": "169.254.125.155/28", - "protocol": "IP" - } - }, - "sequence-id": 1 - } - ] - } - } - ] + { + "config": { + "name": "Customer IPV6 in:40572 out:pop" + }, + "egress": { + "config": { + "incoming-label": 40572, + "next-hop": "2600:2d00:0:1:4000:15:69:2072" } + }, + "name": "Customer IPV6 in:40572 out:pop" } + ] } - } - } -} + } + }, + "name": "default" + } + ] + } +} ``` - +TODO: +* Finalize and update the below paths after the review and testing on any vendor device +* MPLSoGRE/MPLSoGUE packet classification OC need to be defined +* OC for Queueing with shaper need to be defined +* Add json queueing configs in this file - b/408493684 +* Add to OC data models following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 :"pipe-mode": true +* + ## OpenConfig Path and RPC Coverage ```yaml diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/metadata.textproto new file mode 100644 index 00000000000..3cdaad8ac62 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/metadata.textproto @@ -0,0 +1,38 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2beaac46-9b7b-49c4-9bde-62ad530aa5c5" +plan_id: "PF-1.18" +description: "MPLSoGRE and MPLSoGUE QoS" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + aggregate_atomic_update: true + interface_enabled: true + default_network_instance: "default" + next_hop_group_config_unsupported: true + mpls_unsupported: true + gue_gre_decap_unsupported: true + mpls_label_classification_unsupported: true + local_proxy_unsupported: true + static_mpls_unsupported: true + qos_classification_unsupported: true + policy_forwarding_unsupported: true + interface_policy_forwarding_unsupported: true + } +} +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + interface_ref_config_unsupported: true + rib_wecmp: true + wecmp_auto_unsupported: true + isis_loopback_required: true + weighted_ecmp_fixed_packet_verification: true + } +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/mpls_gre_udp_qos.go b/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/mpls_gre_udp_qos.go new file mode 100644 index 00000000000..8735da6c1c2 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gre_udp_qos/mpls_gre_udp_qos.go @@ -0,0 +1,437 @@ +// Package mpls_gre_udp_qos_test tests mplsogre encap functionality. +package mpls_gre_udp_qos_test + +import ( + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + otgconfighelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_config_helpers" + otgvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_validation_helpers" + packetvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/packetvalidationhelpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygot/ygot" +) + +// TestMain calls main function. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag + // GREProtocol is the protocol number for GRE. + GREProtocol = 47 +) + +var ( + top = gosnappi.NewConfig() + aggID string + custPorts = []string{"port1", "port2"} + corePorts = []string{"port3", "port4"} + custIntfIPv4 = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 1500, + IPv4: "169.254.0.11", + IPv4Len: 29, + Subinterface: 20, + } + + coreIntf = attrs.Attributes{ + Desc: "Core_Interface", + IPv4: "194.0.2.1", + IPv6: "2001:10:1:6::1", + MTU: 9202, + IPv4Len: 24, + IPv6Len: 126, + } + + agg1 = &otgconfighelpers.Port{ + Name: "Port-Channel1", + AggMAC: "02:00:01:01:01:07", + Interfaces: []*otgconfighelpers.InterfaceProperties{interface1}, + MemberPorts: []string{"port1", "port2"}, + LagID: 1, + IsLag: true, + } + agg2 = &otgconfighelpers.Port{ + Name: "Port-Channel2", + AggMAC: "02:00:01:01:01:01", + MemberPorts: []string{"port3", "port4"}, + Interfaces: []*otgconfighelpers.InterfaceProperties{interface7}, + LagID: 2, + IsLag: true, + } + + interface1 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.12", + IPv4Gateway: "169.254.0.11", + Name: "Port-Channel1.20", + MAC: "02:00:01:01:01:08", + Vlan: 20, + IPv4Len: 29, + } + + interface7 = &otgconfighelpers.InterfaceProperties{ + IPv4: "194.0.2.2", + IPv6: "2001:10:1:6::2", + IPv4Gateway: "194.0.2.1", + IPv6Gateway: "2001:10:1:6::1", + Name: "Port-Channel2", + MAC: "02:00:01:01:01:02", + IPv4Len: 29, + IPv6Len: 126, + } + // Custom IMIX settings for all flows. + sizeWeightProfile = []otgconfighelpers.SizeWeightPair{ + {Size: 64, Weight: 20}, + {Size: 128, Weight: 20}, + {Size: 256, Weight: 20}, + {Size: 512, Weight: 10}, + {Size: 1500, Weight: 28}, + {Size: 9000, Weight: 2}, + } + // FlowIPv4 consists of MPLSOGRE Encap IPv4 interface IPv4 Payload. + FlowIPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg1.Interfaces[0].Name + ".IPv4"}, + RxNames: []string{agg2.Name + ".IPv4"}, + SizeWeightProfile: &sizeWeightProfile, + Flowrate: 80, + FlowName: "traffic IPv4 interface IPv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg1.AggMAC}, + VLANFlow: &otgconfighelpers.VLANFlowParams{VLANId: 20}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 100, RawPriority: 0, RawPriorityCount: 100}, + } + // flowIPv4Validation consists of flow validation params. + flowIPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name, agg1.Interfaces[0].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowIPv4.FlowName, TolerancePct: 0.5}, + } + validations = []packetvalidationhelpers.ValidationType{ + packetvalidationhelpers.ValidateIPv4Header, + packetvalidationhelpers.ValidateMPLSLayer, + packetvalidationhelpers.ValidateInnerIPv4Header, + } + outerGREIPLayerIPv4 = &packetvalidationhelpers.IPv4Layer{ + Protocol: GREProtocol, + DstIP: "10.99.1.1", + Tos: 96, + TTL: 64, + } + mplsLayer = &packetvalidationhelpers.MPLSLayer{ + Label: 116383, + Tc: 1, + } + innerIPLayerIPv4 = &packetvalidationhelpers.IPv4Layer{ + DstIP: "11.1.1.1", + Tos: 1, + TTL: 63, + } + innerIPLayerIPv6 = &packetvalidationhelpers.IPv6Layer{ + DstIP: "2000:1::1", + TrafficClass: 10, + HopLimit: 63, + } + encapPacketValidation = &packetvalidationhelpers.PacketValidation{ + PortName: "port3", + IPv4Layer: outerGREIPLayerIPv4, + MPLSLayer: mplsLayer, + Validations: validations, + InnerIPLayerIPv4: innerIPLayerIPv4, + InnerIPLayerIPv6: innerIPLayerIPv6, + TCPLayer: &packetvalidationhelpers.TCPLayer{SrcPort: 49152, DstPort: 179}, + UDPLayer: &packetvalidationhelpers.UDPLayer{SrcPort: 49152, DstPort: 3784}, + } +) + +func configureOTG(t *testing.T) { + t.Helper() + top.Captures().Clear() + ate := ondatra.ATE(t, "ate") + + // Create a slice of aggPortData for easier iteration. + aggs := []*otgconfighelpers.Port{agg1, agg2} + + // Configure OTG Interfaces. + for _, agg := range aggs { + otgconfighelpers.ConfigureNetworkInterface(t, top, ate, agg) + } + ate.OTG().PushConfig(t, top) +} + +// ConfigureDut configures DUT for PF-1.18.1. +func ConfigureDut(t *testing.T, dut *ondatra.DUTDevice, ocPFParams cfgplugins.OcPolicyForwardingParams, ocNHGParams cfgplugins.StaticNextHopGroupParams) { + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, custPorts, []*attrs.Attributes{&custIntfIPv4}, aggID) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4, ocPFParams) + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, corePorts, []*attrs.Attributes{&coreIntf}, aggID) + configureStaticRoute(t, dut) + _, ni, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + encapMPLSInGRE(t, dut, pf, ni, ocPFParams, ocNHGParams) + decapMPLSInGRE(t, dut, pf, ni, ocPFParams) + if !deviations.PolicyForwardingOCUnsupported(dut) { + pushPolicyForwardingConfig(t, dut, ni) + } + +} + +// TestSetup configures the DUT and ATE for the test. +func TestSetup(t *testing.T) { + t.Log("PF-1.18.1: Generate DUT Configuration") + dut := ondatra.DUT(t, "dut") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + // Get default parameters for OC Policy Forwarding + ocPFParams := GetDefaultOcPolicyForwardingParams() + ocNHGParams := GetDefaultStaticNextHopGroupParams() + + // Pass ocPFParams to ConfigureDut + ConfigureDut(t, dut, ocPFParams, ocNHGParams) + configureOTG(t) +} + +// GetDefaultStaticNextHopGroupParams provides default parameters for the generator. +// matching the values in the provided JSON example. +func GetDefaultStaticNextHopGroupParams() cfgplugins.StaticNextHopGroupParams { + return cfgplugins.StaticNextHopGroupParams{ + + StaticNHGName: "MPLS_in_GRE_Encap", + NHIPAddr1: "nh_ip_addr_1", + NHIPAddr2: "nh_ip_addr_2", + // TODO: b/417988636 - Set the MplsLabel to the correct value. + } +} + +// GetDefaultOcPolicyForwardingParams provides default parameters for the generator, +// matching the values in the provided JSON example. +func GetDefaultOcPolicyForwardingParams() cfgplugins.OcPolicyForwardingParams { + return cfgplugins.OcPolicyForwardingParams{ + NetworkInstanceName: "DEFAULT", + InterfaceID: "Agg1.10", + AppliedPolicyName: "customer1", + } +} + +func configureInterfaceProperties(t *testing.T, dut *ondatra.DUTDevice, aggID string, a *attrs.Attributes, ocPFParams cfgplugins.OcPolicyForwardingParams) { + _, _, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + + if a.IPv4 != "" { + cfgplugins.InterfacelocalProxyConfig(t, dut, a, aggID) + } + cfgplugins.InterfaceQosClassificationConfig(t, dut, a, aggID) + cfgplugins.InterfacePolicyForwardingConfig(t, dut, a, aggID, pf, ocPFParams) +} + +// function should also include the OC config , within these deviations there should be a switch statement is needed +// Modified to accept pf, ni, and ocPFParams +func encapMPLSInGRE(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ni *oc.NetworkInstance, ocPFParams cfgplugins.OcPolicyForwardingParams, ocNHGParams cfgplugins.StaticNextHopGroupParams) { + cfgplugins.MplsConfig(t, dut) + cfgplugins.QosClassificationConfig(t, dut) + cfgplugins.LabelRangeConfig(t, dut) + cfgplugins.NextHopGroupConfig(t, dut, "v4", ni, ocNHGParams) + cfgplugins.PolicyForwardingConfig(t, dut, "v4", pf, ocPFParams) + cfgplugins.NextHopGroupConfig(t, dut, "multicloudv4", ni, ocNHGParams) + cfgplugins.PolicyForwardingConfig(t, dut, "multicloudv4", pf, ocPFParams) +} + +// TestMPLSOGREEncapIPv4 verifies PF-1.18.3: Verify DSCP marking of encapsulated and decapsulated traffic. +func TestMPLSOGREEncapIPv4(t *testing.T) { + t.Logf("PF-1.18.3: Verify DSCP marking of encapsulated and decapsulated traffic") + ate := ondatra.ATE(t, "ate") + + createflow(t, top, FlowIPv4, true) + sendTraffic(t, ate, "IPv4") + + if err := flowIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("Validation on flows failed (): %q", err) + } + FlowIPv4.IPv4Flow.RawPriority = 1 + FlowIPv4.IPv4Flow.RawPriorityCount = 0 + FlowIPv4.PacketsToSend = 1000 + + createflow(t, top, FlowIPv4, true) + packetvalidationhelpers.ConfigurePacketCapture(t, top, encapPacketValidation) + sendTrafficCapture(t, ate) + if err := flowIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + packetvalidationhelpers.ClearCapture(t, top, ate) + t.Errorf("Validation on flows failed (): %q", err) + } + + if err := packetvalidationhelpers.CaptureAndValidatePackets(t, ate, encapPacketValidation); err != nil { + packetvalidationhelpers.ClearCapture(t, top, ate) + t.Errorf("Capture And ValidatePackets Failed (): %q", err) + } + packetvalidationhelpers.ClearCapture(t, top, ate) +} + +func sendTraffic(t *testing.T, ate *ondatra.ATEDevice, traffictype string) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + if traffictype == "IPv4" { + flowIPv4Validation.IsIPv4Interfaceresolved(t, ate) + } + ate.OTG().StartTraffic(t) + time.Sleep(120 * time.Second) + ate.OTG().StopTraffic(t) +} + +func createflow(t *testing.T, top gosnappi.Config, params *otgconfighelpers.Flow, clearFlows bool) { + t.Helper() + if clearFlows { + top.Flows().Clear() + } + params.CreateFlow(top) + params.AddEthHeader() + params.AddVLANHeader() + if params.IPv4Flow != nil { + params.AddIPv4Header() + } + if params.IPv6Flow != nil { + params.AddIPv6Header() + } + if params.TCPFlow != nil { + params.AddTCPHeader() + } + if params.UDPFlow != nil { + params.AddUDPHeader() + } +} + +func configureInterfaces(t *testing.T, dut *ondatra.DUTDevice, dutPorts []string, subinterfaces []*attrs.Attributes, aggID string) { + t.Helper() + d := gnmi.OC() + var dutAggPorts []*ondatra.Port + for _, port := range dutPorts { + dutAggPorts = append(dutAggPorts, dut.Port(t, port)) + } + if deviations.AggregateAtomicUpdate(dut) { + cfgplugins.DeleteAggregate(t, dut, aggID, dutAggPorts) + cfgplugins.SetupAggregateAtomically(t, dut, aggID, dutAggPorts) + } + + lacp := &oc.Lacp_Interface{Name: ygot.String(aggID)} + lacp.LacpMode = oc.Lacp_LacpActivityType_ACTIVE + lacpPath := d.Lacp().Interface(aggID) + fptest.LogQuery(t, "LACP", lacpPath.Config(), lacp) + gnmi.Replace(t, dut, lacpPath.Config(), lacp) + // TODO - to remove this sleep later + time.Sleep(5 * time.Second) + + agg := &oc.Interface{Name: ygot.String(aggID)} + configDUTInterface(agg, subinterfaces, dut) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_LACP + agg.Type = ieee8023adLag + aggPath := d.Interface(aggID) + fptest.LogQuery(t, aggID, aggPath.Config(), agg) + gnmi.Replace(t, dut, aggPath.Config(), agg) + + for _, port := range dutAggPorts { + holdTimeConfig := &oc.Interface_HoldTime{ + Up: ygot.Uint32(3000), + Down: ygot.Uint32(150), + } + intfPath := gnmi.OC().Interface(port.Name()) + gnmi.Update(t, dut, intfPath.HoldTime().Config(), holdTimeConfig) + } +} + +func configDUTInterface(i *oc.Interface, subinterfaces []*attrs.Attributes, dut *ondatra.DUTDevice) { + for _, a := range subinterfaces { + i.Description = ygot.String(a.Desc) + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s1 := i.GetOrCreateSubinterface(0) + b4 := s1.GetOrCreateIpv4() + b6 := s1.GetOrCreateIpv6() + b4.Mtu = ygot.Uint16(a.MTU) + b6.Mtu = ygot.Uint32(uint32(a.MTU)) + if deviations.InterfaceEnabled(dut) { + b4.Enabled = ygot.Bool(true) + } + if a.Subinterface != 0 { + s := i.GetOrCreateSubinterface(a.Subinterface) + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(uint16(a.Subinterface)) + configureInterfaceAddress(dut, s, a) + } else { + configureInterfaceAddress(dut, s1, a) + } + } +} + +func configureInterfaceAddress(dut *ondatra.DUTDevice, s *oc.Interface_Subinterface, a *attrs.Attributes) { + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + if a.IPv4 != "" { + a4 := s4.GetOrCreateAddress(a.IPv4) + a4.PrefixLength = ygot.Uint8(a.IPv4Len) + } + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + if a.IPv6 != "" { + s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(a.IPv6Len) + } + + if a.IPv6Sec != "" { + s6_2 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6_2.Enabled = ygot.Bool(true) + } + s6_2.GetOrCreateAddress(a.IPv6Sec).PrefixLength = ygot.Uint8(a.IPv6Len) + } +} + +func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + b := &gnmi.SetBatch{} + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: "10.99.1.0/24", + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString("194.0.2.2"), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) +} + +func pushPolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance) { + t.Helper() + niPath := gnmi.OC().NetworkInstance(ni.GetName()).Config() + gnmi.Replace(t, dut, niPath, ni) +} + +func decapMPLSInGRE(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ni *oc.NetworkInstance, ocPFParams cfgplugins.OcPolicyForwardingParams) { + t.Helper() + cfgplugins.MplsConfig(t, dut) + cfgplugins.QosClassificationConfig(t, dut) + cfgplugins.LabelRangeConfig(t, dut) + cfgplugins.DecapGroupConfigGre(t, dut, pf, ocPFParams) + cfgplugins.MPLSStaticLSPConfig(t, dut, ni, ocPFParams) +} + +func sendTrafficCapture(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + cs := packetvalidationhelpers.StartCapture(t, ate) + ate.OTG().StartTraffic(t) + time.Sleep(60 * time.Second) + ate.OTG().StopTraffic(t) + time.Sleep(60 * time.Second) + packetvalidationhelpers.StopCapture(t, ate, cs) +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/README.md index 2bc0fe64851..36baf69bc90 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/README.md @@ -1,4 +1,4 @@ -# PF-1.20 - MPLSoGUE IPV4 decapsulation of IPV4/IPV6 payload scale test +# PF-1.20: MPLSoGUE IPV4 decapsulation of IPV4/IPV6 payload scale test ## Summary This test verifies scaling of MPLSoGUE decapsulation of IP traffic using static MPLS LSP configuration. MPLSoGUE Traffic on ingress to the DUT is decapsulated and IPV4/IPV6 payload is forwarded towards the IPV4/IPV6 egress nexthop. @@ -47,7 +47,6 @@ Verify: * Device can achieve the maximum interface scale on the device * Entire static label range is usable and functional by sending traffic across the entire label range - ## OpenConfig Path and RPC Coverage TODO: Finalize and update the below paths after the review and testing on any vendor device. @@ -72,4 +71,4 @@ rpcs: ## Minimum DUT platform requirement -FFF \ No newline at end of file +FFF diff --git a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/metadata.textproto new file mode 100644 index 00000000000..c1d07985af3 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_scale_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "01e97eb5-b01c-4d26-a1e4-8cf29d1e2245" +plan_id: "PF-1.20" +description: "MPLSoGUE IPV4 decapsulation of IPV4/IPV6 payload scale test" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/README.md b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/README.md index 9edc51f32b3..9f528b53acc 100644 --- a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/README.md +++ b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/README.md @@ -1,4 +1,4 @@ -# PF-1.19 - MPLSoGUE IPV4 decapsulation of IPV4/IPV6 payload +# PF-1.19: MPLSoGUE IPV4 decapsulation of IPV4/IPV6 payload ## Summary This test verifies MPLSoGUE decapsulation of IP traffic using static MPLS LSP configuration. MPLSoGUE Traffic on ingress to the DUT is decapsulated and IPV4/IPV6 payload is forwarded towards the IPV4/IPV6 egress nexthop. @@ -25,7 +25,7 @@ Test uses aggregate 802.3ad bundled interfaces (Aggregate Interfaces). * Egress Ports: Aggregate1 * Traffic is forwarded (egress) on Aggregate1 (ATE Ports 1,2) . -## PF-1.19.1 Generate config for MPLS in GRE decap and push to DUT +## PF-1.19.1: Generate config for MPLS in GRE decap and push to DUT #### Configuration #### Aggregate1 is the egress port having following configuration: @@ -168,57 +168,16 @@ Generate traffic (100K packets at 1000 pps) on ATE Ports 3,4,5,6 having: Verify: * No packet loss when forwarding with counters incrementing corresponding to traffic -## Canonical OpenConfig for policy-forwarding matching ipv4 and decapsulate GRE +## Canonical OC ```json { "network-instances": { - "network-instance": { - "DEFAULT": { - "name": "default", - "policy-forwarding": { - "policies": { - "policy": [ - { - "config": { - "policy-id": "decap MPLS in GRE" - }, - "rules": { - "rule": [ - { - "config": { - "sequence-id": 1 - }, - "ipv4": { - "config": { - "destination-address": "169.254.125.155/28", - "protocol": "IP" - } - }, - "action": { - "decapsulate-mpls-in-udp": true - }, - "sequence-id": 1 - } - ] - } - } - ] - } + "network-instance": [ + { + "config": { + "name": "default" }, "mpls": { - "global": { - "interface-attributes": { - "interface": [ - { - "config": { - "interface-id": "Aggregate4", - "mpls-enabled": false - }, - "interface-id": "Aggregate4" - } - ] - } - }, "lsps": { "static-lsps": { "static-lsp": [ @@ -229,10 +188,10 @@ Verify: "egress": { "config": { "incoming-label": 40571, - "next-hop": "169.254.1.138", - "pipe-mode": true, # TODO: Add to OC data models, following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 + "next-hop": "169.254.1.138" } - } + }, + "name": "Customer IPV4 in:40571 out:pop" }, { "config": { @@ -241,29 +200,51 @@ Verify: "egress": { "config": { "incoming-label": 40572, - "next-hop": "2600:2d00:0:1:4000:15:69:2072", - "pipe-mode": true, # TODO: Add to OC data models, following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 + "next-hop": "2600:2d00:0:1:4000:15:69:2072" } - } - }, - { - "config": { - "name": "Customer multicast in:40573 out:pop" }, - "egress": { - "config": { - "incoming-label": 40573, - "next-hop": "239.0.1.1", # Multicast traffic must be sent out with L2 multicast header based on IP Multicast address even though there is no PIM on the egress interface - "pipe-mode": true, # TODO: Add to OC data models, following https: //datatracker.ietf.org/doc/html/rfc3270#section-2.6.2 - } - } + "name": "Customer IPV6 in:40572 out:pop" } ] } } + }, + "name": "default", + "policy-forwarding": { + "policies": { + "policy": [ + { + "config": { + "policy-id": "customer10" + }, + "policy-id": "customer10", + "rules": { + "rule": [ + { + "action": { + "config": { + "decapsulate-gue": true + } + }, + "config": { + "sequence-id": 10 + }, + "ipv4": { + "config": { + "destination-address": "169.254.125.155/28", + "protocol": 4 + } + }, + "sequence-id": 10 + } + ] + } + } + ] + } } } - } + ] } } ``` diff --git a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/metadata.textproto b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/metadata.textproto new file mode 100644 index 00000000000..4ac209ca1b8 --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/metadata.textproto @@ -0,0 +1,24 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2beaac46-9b7b-49c4-9bde-62ad530aa5c8" +plan_id: "PF-1.19" +description: "MPLSoGUE IPV4 decapsulation of IPV4/IPV6 payload" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + local_proxy_unsupported: true + qos_classification_unsupported: true + policy_forwarding_unsupported: true + mpls_unsupported: true + mpls_label_classification_unsupported: true + aggregate_atomic_update: true + interface_enabled: true + gue_gre_decap_unsupported: true + static_mpls_unsupported: true + default_network_instance: "default" + } +} diff --git a/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/mpls_gue_ipv4_decap_test.go b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/mpls_gue_ipv4_decap_test.go new file mode 100644 index 00000000000..941a85bdaaf --- /dev/null +++ b/feature/policy_forwarding/otg_tests/mpls_gue_ipv4_decap_test/mpls_gue_ipv4_decap_test.go @@ -0,0 +1,534 @@ +// Package mpls_gue_ipv4_decap_test tests mplsogue decap functionality. +package mpls_gue_ipv4_decap_test + +import ( + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + otgconfighelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_config_helpers" + otgvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/otg_validation_helpers" + packetvalidationhelpers "github.com/openconfig/featureprofiles/internal/otg_helpers/packetvalidationhelpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygot/ygot" +) + +// TestMain calls main function. +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +const ( + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag +) + +var ( + top = gosnappi.NewConfig() + aggID string + custPorts = []string{"port1", "port2"} + corePorts = []string{"port3", "port4"} + custIntfIPv4 = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 1500, + IPv4: "169.254.0.11", + IPv4Len: 29, + Subinterface: 20, + } + custIntfIPv6 = attrs.Attributes{ + Desc: "Customer_connectv6", + MTU: 1500, + IPv6: "2600:2d00:0:1:8000:10:0:ca31", + IPv6Sec: "2600:2d00:0:1:8000:10:0:ca33", + IPv6Len: 125, + Subinterface: 21, + } + + custIntfdualStack = attrs.Attributes{ + Desc: "Customer_connect_dualstack", + MTU: 1500, + IPv4: "169.254.0.27", + IPv4Len: 29, + IPv6: "2600:2d00:0:1:7000:10:0:ca31", + IPv6Sec: "2600:2d00:0:1:7000:10:0:ca33", + IPv6Len: 125, + Subinterface: 22, + } + custIntfIPv4MultiCloud = attrs.Attributes{ + Desc: "Customer_connect_multicloud", + MTU: 1500, + IPv4: "169.254.0.33", + IPv4Len: 30, + Subinterface: 23, + } + custIntfIPv4JumboMTU = attrs.Attributes{ + Desc: "Customer_connect", + MTU: 9066, + IPv4: "169.254.0.53", + IPv4Len: 29, + Subinterface: 26, + } + coreIntf = attrs.Attributes{ + Desc: "Core_Interface", + IPv4: "194.0.2.1", + IPv6: "2001:10:1:6::1", + MTU: 9202, + IPv4Len: 24, + IPv6Len: 126, + } + + agg1 = &otgconfighelpers.Port{ + Name: "Port-Channel1", + AggMAC: "02:00:01:01:01:07", + Interfaces: []*otgconfighelpers.InterfaceProperties{interface1, interface2, interface3, interface4, interface8}, + MemberPorts: []string{"port1", "port2"}, + LagID: 1, + IsLag: true, + } + agg2 = &otgconfighelpers.Port{ + Name: "Port-Channel2", + AggMAC: "02:00:01:01:01:01", + MemberPorts: []string{"port3", "port4"}, + Interfaces: []*otgconfighelpers.InterfaceProperties{interface7}, + LagID: 2, + IsLag: true, + } + + interface1 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.12", + IPv4Gateway: "169.254.0.11", + Name: "Port-Channel1.20", + MAC: "02:00:01:01:01:08", + Vlan: 20, + IPv4Len: 29, + } + interface2 = &otgconfighelpers.InterfaceProperties{ + IPv6: "2600:2d00:0:1:8000:10:0:ca32", + IPv6Gateway: "2600:2d00:0:1:8000:10:0:ca31", + MAC: "02:00:01:01:01:09", + Name: "Port-Channel1.21", + Vlan: 21, + IPv6Len: 125, + } + interface3 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.26", + IPv4Gateway: "169.254.0.27", + IPv6: "2600:2d00:0:1:7000:10:0:ca32", + IPv6Gateway: "2600:2d00:0:1:7000:10:0:ca31", + MAC: "02:00:01:01:01:10", + Name: "Port-Channel1.22", + Vlan: 22, + IPv4Len: 29, + IPv6Len: 125, + } + interface4 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.34", + IPv4Gateway: "169.254.0.33", + Name: "Port-Channel1.23", + MAC: "02:00:01:01:01:11", + Vlan: 23, + IPv4Len: 30, + } + interface8 = &otgconfighelpers.InterfaceProperties{ + IPv4: "169.254.0.54", + IPv4Gateway: "169.254.0.53", + Name: "Port-Channel1.26", + MAC: "02:00:01:01:01:13", + Vlan: 26, + IPv4Len: 29, + } + interface7 = &otgconfighelpers.InterfaceProperties{ + IPv4: "194.0.2.2", + IPv6: "2001:10:1:6::2", + IPv4Gateway: "194.0.2.1", + IPv6Gateway: "2001:10:1:6::1", + Name: "Port-Channel2", + MAC: "02:00:01:01:01:02", + IPv4Len: 29, + IPv6Len: 126, + } + // Custom IMIX settings for all flows. + sizeWeightProfile = []otgconfighelpers.SizeWeightPair{ + {Size: 64, Weight: 20}, + {Size: 128, Weight: 20}, + {Size: 256, Weight: 20}, + {Size: 512, Weight: 10}, + {Size: 1500, Weight: 28}, + {Size: 9000, Weight: 2}, + } + + flowResolveArp = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg2.Name}}, + } + nextHopResolutionIPv4 = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[0].Name, agg1.Interfaces[2].Name, agg1.Interfaces[3].Name, agg1.Interfaces[4].Name}}, + } + nextHopResolutionIPv6 = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[1].Name, agg1.Interfaces[2].Name}}, + } + // FlowOuterIPv4 Decap IPv4 Interface IPv4 Payload traffic params Outer Header. + FlowOuterIPv4 = &otgconfighelpers.Flow{ + TxNames: []string{agg2.Name + ".IPv4"}, + RxNames: []string{agg1.Interfaces[0].Name + ".IPv4"}, + SizeWeightProfile: &sizeWeightProfile, + Flowrate: 45, + FlowName: "MPLSOGUE traffic IPv4 interface IPv4 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg2.AggMAC}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "100.64.0.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 10000}, + MPLSFlow: &otgconfighelpers.MPLSFlowParams{MPLSLabel: 99991, MPLSExp: 7}, + UDPFlow: &otgconfighelpers.UDPFlowParams{UDPSrcPort: 49152, UDPDstPort: 6635}, + } + // FlowOuterIPv4Validation MPLSOGUE traffic IPv4 interface IPv4 Payload. + FlowOuterIPv4Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[0].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowOuterIPv4.FlowName, TolerancePct: 0.5}, + } + // FlowInnerIPv4 Inner Header IPv4 Payload. + FlowInnerIPv4 = &otgconfighelpers.Flow{ + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "22.1.1.1", IPv4Dst: "21.1.1.1", IPv4SrcCount: 10000, RawPriority: 0, RawPriorityCount: 255}, + TCPFlow: &otgconfighelpers.TCPFlowParams{TCPSrcPort: 49152, TCPDstPort: 80, TCPSrcCount: 10000}, + } + // FlowOuterIPv6 Decap IPv6 Interface IPv6 Payload traffic params Outer Header. + FlowOuterIPv6 = &otgconfighelpers.Flow{ + TxNames: []string{agg2.Name + ".IPv4"}, + RxNames: []string{agg1.Interfaces[1].Name + ".IPv6"}, + SizeWeightProfile: &sizeWeightProfile, + Flowrate: 45, + FlowName: "MPLSOGUE traffic IPv6 interface IPv6 Payload", + EthFlow: &otgconfighelpers.EthFlowParams{SrcMAC: agg2.AggMAC}, + IPv4Flow: &otgconfighelpers.IPv4FlowParams{IPv4Src: "100.64.0.1", IPv4Dst: "11.1.1.1", IPv4SrcCount: 10000}, + MPLSFlow: &otgconfighelpers.MPLSFlowParams{MPLSLabel: 99992, MPLSExp: 7}, + UDPFlow: &otgconfighelpers.UDPFlowParams{UDPSrcPort: 49152, UDPDstPort: 6635}, + } + // FlowOuterIPv6Validation MPLSOGUE traffic IPv6 interface IPv6 Payload. + FlowOuterIPv6Validation = &otgvalidationhelpers.OTGValidation{ + Interface: &otgvalidationhelpers.InterfaceParams{Names: []string{agg1.Interfaces[1].Name}, Ports: append(agg1.MemberPorts, agg2.MemberPorts...)}, + Flow: &otgvalidationhelpers.FlowParams{Name: FlowOuterIPv6.FlowName, TolerancePct: 0.5}, + } + // FlowInnerIPv6 Inner Header IPv6 Payload. + FlowInnerIPv6 = &otgconfighelpers.Flow{ + IPv6Flow: &otgconfighelpers.IPv6FlowParams{IPv6Src: "2000:1::1", IPv6Dst: "3000:1::1", IPv6SrcCount: 10000, TrafficClass: 0, TrafficClassCount: 255}, + TCPFlow: &otgconfighelpers.TCPFlowParams{TCPSrcPort: 49152, TCPDstPort: 80, TCPSrcCount: 10000}, + } + validationsIPv4 = []packetvalidationhelpers.ValidationType{ + packetvalidationhelpers.ValidateIPv4Header, + packetvalidationhelpers.ValidateTCPHeader, + } + decapValidationIPv4 = &packetvalidationhelpers.PacketValidation{ + PortName: "port1", + CaptureName: "ipv4_decap", + Validations: validationsIPv4, + IPv4Layer: &packetvalidationhelpers.IPv4Layer{DstIP: "21.1.1.1", Tos: 10, TTL: 64, Protocol: packetvalidationhelpers.TCP}, + TCPLayer: &packetvalidationhelpers.TCPLayer{SrcPort: 49152, DstPort: 80}, + } +) + +func ConfigureOTG(t *testing.T) { + t.Helper() + top.Captures().Clear() + ate := ondatra.ATE(t, "ate") + + // Create a slice of aggPortData for easier iteration + aggs := []*otgconfighelpers.Port{agg1, agg2} + + // Configure OTG Interfaces + for _, agg := range aggs { + otgconfighelpers.ConfigureNetworkInterface(t, top, ate, agg) + } + ate.OTG().PushConfig(t, top) +} + +// PF-1.19.1: Generate DUT Configuration +func ConfigureDut(t *testing.T, dut *ondatra.DUTDevice, ocPFParams cfgplugins.OcPolicyForwardingParams) { + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, custPorts, []*attrs.Attributes{&custIntfIPv4, &custIntfIPv6, &custIntfdualStack, &custIntfIPv4MultiCloud, &custIntfIPv4JumboMTU}, aggID) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv6, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfdualStack, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4MultiCloud, ocPFParams) + configureInterfaceProperties(t, dut, aggID, &custIntfIPv4JumboMTU, ocPFParams) + aggID = netutil.NextAggregateInterface(t, dut) + configureInterfaces(t, dut, corePorts, []*attrs.Attributes{&coreIntf}, aggID) + configureStaticRoute(t, dut) + _, ni, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + DecapMPLSInGUE(t, dut, pf, ni, ocPFParams) +} + +func TestSetup(t *testing.T) { + t.Log("PF-1.19.1: Generate DUT Configuration") + dut := ondatra.DUT(t, "dut") + fptest.ConfigureDefaultNetworkInstance(t, dut) + + // Get default parameters for OC Policy Forwarding + ocPFParams := GetDefaultOcPolicyForwardingParams() + + // Pass ocPFParams to ConfigureDut + ConfigureDut(t, dut, ocPFParams) + ConfigureOTG(t) +} + +// GetDefaultOcPolicyForwardingParams provides default parameters for the generator, +// matching the values in the provided JSON example. +func GetDefaultOcPolicyForwardingParams() cfgplugins.OcPolicyForwardingParams { + return cfgplugins.OcPolicyForwardingParams{ + NetworkInstanceName: "DEFAULT", + InterfaceID: "Agg1.10", + AppliedPolicyName: "customer1", + } +} + +func configureInterfaceProperties(t *testing.T, dut *ondatra.DUTDevice, aggID string, a *attrs.Attributes, ocPFParams cfgplugins.OcPolicyForwardingParams) { + _, _, pf := cfgplugins.SetupPolicyForwardingInfraOC(ocPFParams.NetworkInstanceName) + + if a.IPv4 != "" { + cfgplugins.InterfacelocalProxyConfig(t, dut, a, aggID) + } + cfgplugins.InterfaceQosClassificationConfig(t, dut, a, aggID) + cfgplugins.InterfacePolicyForwardingConfig(t, dut, a, aggID, pf, ocPFParams) +} + +// function should also include the OC config , within these deviations there should be a switch statement is needed +// Modified to accept pf, ni, and ocPFParams +func DecapMPLSInGUE(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ni *oc.NetworkInstance, ocPFParams cfgplugins.OcPolicyForwardingParams) { + cfgplugins.MplsConfig(t, dut) + cfgplugins.QosClassificationConfig(t, dut) + cfgplugins.LabelRangeConfig(t, dut) + cfgplugins.DecapGroupConfigGue(t, dut, pf, ocPFParams) + cfgplugins.MPLSStaticLSPConfig(t, dut, ni, ocPFParams) + if !deviations.PolicyForwardingOCUnsupported(dut) { + PushPolicyForwardingConfig(t, dut, ni) + } +} + +// PF-1.19.2: Verify PF MPLSoGUE Decap action for IPv4 and IPv6 traffic. +func TestMPLSOGUEDecapIPv4AndIPv6(t *testing.T) { + ate := ondatra.ATE(t, "ate") + t.Log("PF-1.19.2: Verify MPLSoGUE decapsulate action for IPv4 and IPv6 payload") + createflow(t, top, FlowOuterIPv4, FlowInnerIPv4, true) + createflow(t, top, FlowOuterIPv6, FlowInnerIPv6, false) + sendTraffic(t, ate) + if err := FlowOuterIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } + if err := FlowOuterIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } +} + +// PF-1.19.5: Verify MPLSoGUE DSCP/TTL preserve operation. +func TestMPLSOGUEDecapInnerPayloadPreserve(t *testing.T) { + ate := ondatra.ATE(t, "ate") + t.Log("PF-1.19.5: Verify MPLSoGUE DSCP/TTL preserve operation") + packetvalidationhelpers.ClearCapture(t, top, ate) + updateFlow(t, FlowOuterIPv4, FlowInnerIPv4, true, 100, 1000) + packetvalidationhelpers.ConfigurePacketCapture(t, top, decapValidationIPv4) + sendTrafficCapture(t, ate) + if err := FlowOuterIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } + if err := packetvalidationhelpers.CaptureAndValidatePackets(t, ate, decapValidationIPv4); err != nil { + t.Errorf("CaptureAndValidatePackets(): got err: %q", err) + } +} + +// PF-1.19.6: Verify IPV4/IPV6 nexthop resolution of decap traffic +func TestMPLSOGUEDecapNextHopResolution(t *testing.T) { + ate := ondatra.ATE(t, "ate") + totalPackets := uint32(100000) + pps := uint64(1000) + t.Log("PF-1.19.6: Verify IPV4/IPV6 nexthop resolution of decap traffic") + updateFlow(t, FlowOuterIPv4, FlowInnerIPv4, true, pps, totalPackets) + updateFlow(t, FlowOuterIPv6, FlowInnerIPv6, false, pps, totalPackets) + // Make sure the next hop resolution is done for IPv4 and IPv6 Interfaces facing towards customer Interfaces in OTG. + nextHopResolutionIPv4.IsIPv4Interfaceresolved(t, ate) + nextHopResolutionIPv6.IsIPv6Interfaceresolved(t, ate) + sendTraffic(t, ate) + if err := FlowOuterIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } + if err := FlowOuterIPv6Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q, want nil", err) + } +} + +func sendTraffic(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + flowResolveArp.IsIPv4Interfaceresolved(t, ate) + ate.OTG().StartTraffic(t) + time.Sleep(120 * time.Second) + ate.OTG().StopTraffic(t) +} + +func sendTrafficCapture(t *testing.T, ate *ondatra.ATEDevice) { + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + flowResolveArp.IsIPv4Interfaceresolved(t, ate) + cs := packetvalidationhelpers.StartCapture(t, ate) + ate.OTG().StartTraffic(t) + time.Sleep(60 * time.Second) + ate.OTG().StopTraffic(t) + packetvalidationhelpers.StopCapture(t, ate, cs) +} + +func createflow(t *testing.T, top gosnappi.Config, paramsOuter *otgconfighelpers.Flow, paramsInner *otgconfighelpers.Flow, clearFlows bool) { + if clearFlows { + top.Flows().Clear() + } + paramsOuter.CreateFlow(top) + paramsOuter.AddEthHeader() + paramsOuter.AddIPv4Header() + paramsOuter.AddUDPHeader() + paramsOuter.AddMPLSHeader() + if paramsInner.IPv4Flow != nil { + *paramsOuter.IPv4Flow = *paramsInner.IPv4Flow + paramsOuter.AddIPv4Header() + } + if paramsInner.IPv6Flow != nil { + paramsOuter.IPv6Flow = paramsInner.IPv6Flow + paramsOuter.AddIPv6Header() + } + if paramsInner.TCPFlow != nil { + paramsOuter.TCPFlow = paramsInner.TCPFlow + paramsOuter.AddTCPHeader() + } + if paramsInner.UDPFlow != nil { + *paramsOuter.UDPFlow = *paramsInner.UDPFlow + paramsOuter.AddUDPHeader() + } +} + +func updateFlow(t *testing.T, paramsOuter *otgconfighelpers.Flow, paramsInner *otgconfighelpers.Flow, clearFlows bool, pps uint64, totalPackets uint32) { + paramsOuter.PacketsToSend = totalPackets + paramsOuter.PpsRate = pps + paramsOuter.Flowrate = 0 + if paramsInner.IPv6Flow != nil { + paramsInner.IPv6Flow.TrafficClassCount = 0 + paramsInner.IPv6Flow.TrafficClass = 10 + } + if paramsInner.IPv4Flow != nil { + paramsInner.IPv4Flow.RawPriorityCount = 0 + paramsInner.IPv4Flow.RawPriority = 10 + if paramsInner.TCPFlow != nil { + paramsInner.TCPFlow.TCPSrcCount = 0 + paramsInner.TCPFlow.TCPSrcPort = 49152 + } + paramsOuter.IPv4Flow.IPv4Src = "100.64.0.1" + paramsOuter.IPv4Flow.IPv4Dst = "11.1.1.1" + } + createflow(t, top, paramsOuter, paramsInner, clearFlows) +} + +func configureInterfaces(t *testing.T, dut *ondatra.DUTDevice, dutPorts []string, subinterfaces []*attrs.Attributes, aggID string) { + t.Helper() + d := gnmi.OC() + dutAggPorts := []*ondatra.Port{} + for _, port := range dutPorts { + dutAggPorts = append(dutAggPorts, dut.Port(t, port)) + } + if deviations.AggregateAtomicUpdate(dut) { + cfgplugins.DeleteAggregate(t, dut, aggID, dutAggPorts) + cfgplugins.SetupAggregateAtomically(t, dut, aggID, dutAggPorts) + } + + lacp := &oc.Lacp_Interface{Name: ygot.String(aggID)} + lacp.LacpMode = oc.Lacp_LacpActivityType_ACTIVE + lacpPath := d.Lacp().Interface(aggID) + fptest.LogQuery(t, "LACP", lacpPath.Config(), lacp) + gnmi.Replace(t, dut, lacpPath.Config(), lacp) + // TODO - to remove this sleep later + time.Sleep(5 * time.Second) + + agg := &oc.Interface{Name: ygot.String(aggID)} + configDUTInterface(agg, subinterfaces, dut) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_LACP + agg.Type = ieee8023adLag + aggPath := d.Interface(aggID) + fptest.LogQuery(t, aggID, aggPath.Config(), agg) + gnmi.Replace(t, dut, aggPath.Config(), agg) + + for _, port := range dutAggPorts { + holdTimeConfig := &oc.Interface_HoldTime{ + Up: ygot.Uint32(3000), + Down: ygot.Uint32(150), + } + intfPath := gnmi.OC().Interface(port.Name()) + gnmi.Update(t, dut, intfPath.HoldTime().Config(), holdTimeConfig) + } +} +func configDUTInterface(i *oc.Interface, subinterfaces []*attrs.Attributes, dut *ondatra.DUTDevice) { + for _, a := range subinterfaces { + i.Description = ygot.String(a.Desc) + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + s1 := i.GetOrCreateSubinterface(0) + b4 := s1.GetOrCreateIpv4() + b6 := s1.GetOrCreateIpv6() + b4.Mtu = ygot.Uint16(a.MTU) + b6.Mtu = ygot.Uint32(uint32(a.MTU)) + if deviations.InterfaceEnabled(dut) { + b4.Enabled = ygot.Bool(true) + } + if a.Subinterface != 0 { + s := i.GetOrCreateSubinterface(a.Subinterface) + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().SetVlanId(uint16(a.Subinterface)) + configureInterfaceAddress(dut, s, a) + } else { + configureInterfaceAddress(dut, s1, a) + } + } +} +func configureInterfaceAddress(dut *ondatra.DUTDevice, s *oc.Interface_Subinterface, a *attrs.Attributes) { + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + if a.IPv4 != "" { + a4 := s4.GetOrCreateAddress(a.IPv4) + a4.PrefixLength = ygot.Uint8(a.IPv4Len) + } + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + if a.IPv6 != "" { + s6.GetOrCreateAddress(a.IPv6).PrefixLength = ygot.Uint8(a.IPv6Len) + } + + if a.IPv6Sec != "" { + s6_2 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6_2.Enabled = ygot.Bool(true) + } + s6_2.GetOrCreateAddress(a.IPv6Sec).PrefixLength = ygot.Uint8(a.IPv6Len) + } +} + +func configureStaticRoute(t *testing.T, dut *ondatra.DUTDevice) { + b := &gnmi.SetBatch{} + sV4 := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: "10.99.1.0/24", + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + "0": oc.UnionString("194.0.2.2"), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { + t.Fatalf("Failed to configure IPv4 static route: %v", err) + } + b.Set(t, dut) +} + +func PushPolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance) { + t.Helper() + niPath := gnmi.OC().NetworkInstance(ni.GetName()).Config() + gnmi.Replace(t, dut, niPath, ni) +} diff --git a/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go b/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go deleted file mode 100644 index ca928ff6b5a..00000000000 --- a/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go +++ /dev/null @@ -1,570 +0,0 @@ -/* - Copyright 2022 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 - - https://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 policy_based_vrf_selection_test - -import ( - "strconv" - "testing" - "time" - - "github.com/open-traffic-generator/snappi/gosnappi" - "github.com/openconfig/featureprofiles/internal/attrs" - "github.com/openconfig/featureprofiles/internal/deviations" - "github.com/openconfig/featureprofiles/internal/fptest" - "github.com/openconfig/featureprofiles/internal/otgutils" - "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/gnmi" - "github.com/openconfig/ondatra/gnmi/oc" - "github.com/openconfig/testt" - "github.com/openconfig/ygot/ygot" -) - -const ( - trafficDuration = 1 * time.Minute - ipv4PrefixLen = 30 - ipv6PrefixLen = 126 -) - -// testArgs holds the objects needed by a test case. -type testArgs struct { - dut *ondatra.DUTDevice - ate *ondatra.ATEDevice - top gosnappi.Config - policyName string - iptype string - protocol oc.E_PacketMatchTypes_IP_PROTOCOL -} - -var ( - dutPort1 = attrs.Attributes{ - Desc: "dutPort1", - MAC: "02:00:01:01:01:01", - IPv4: "192.0.2.1", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:1", - IPv6Len: ipv6PrefixLen, - } - - atePort1 = attrs.Attributes{ - Name: "atePort1", - MAC: "02:00:02:01:01:01", - IPv4: "192.0.2.2", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:2", - IPv6Len: ipv6PrefixLen, - } - - dutPort2 = attrs.Attributes{ - Desc: "dutPort2", - MAC: "01:00:01:01:01:01", - IPv4: "192.0.2.5", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:5", - IPv6Len: ipv6PrefixLen, - } - - atePort2 = attrs.Attributes{ - Name: "atePort2", - MAC: "00:12:01:00:00:01", - IPv4: "192.0.2.6", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:6", - IPv6Len: ipv6PrefixLen, - } - - dutPort2Vlan10 = attrs.Attributes{ - Desc: "dutPort2Vlan10", - MAC: "01:00:01:01:01:01", - IPv4: "192.0.2.9", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:9", - IPv6Len: ipv6PrefixLen, - } - - atePort2Vlan10 = attrs.Attributes{ - Name: "atePort2Vlan10", - MAC: "00:12:01:00:00:01", - IPv4: "192.0.2.10", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:a", - IPv6Len: ipv6PrefixLen, - } - - dutPort2Vlan20 = attrs.Attributes{ - Desc: "dutPort2Vlan20", - MAC: "01:00:01:01:01:01", - IPv4: "192.0.2.13", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:d", - IPv6Len: ipv6PrefixLen, - } - - atePort2Vlan20 = attrs.Attributes{ - Name: "atePort2Vlan20", - MAC: "00:12:01:00:00:01", - IPv4: "192.0.2.14", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:e", - IPv6Len: ipv6PrefixLen, - } - - dutPort2Vlan30 = attrs.Attributes{ - Desc: "dutPort2Vlan30", - MAC: "01:00:01:01:01:01", - IPv4: "192.0.2.17", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:11", - IPv6Len: ipv6PrefixLen, - } - - atePort2Vlan30 = attrs.Attributes{ - Name: "atePort2Vlan30", - MAC: "00:12:01:00:00:01", - IPv4: "192.0.2.18", - IPv4Len: ipv4PrefixLen, - IPv6: "2001:0db8::192:0:2:12", - IPv6Len: ipv6PrefixLen, - } -) - -func TestMain(m *testing.M) { - fptest.RunTests(m) -} - -// configureATE configures port1, port2 and vlans on port2 on the ATE. -func configureATE(t *testing.T, ate *ondatra.ATEDevice, dut *ondatra.DUTDevice) gosnappi.Config { - top := gosnappi.NewConfig() - - p1 := ate.Port(t, "port1") - top.Ports().Add().SetName(p1.ID()) - srcDev := top.Devices().Add().SetName(atePort1.Name) - ethSrc := srcDev.Ethernets().Add().SetName(atePort1.Name + ".eth").SetMac(atePort1.MAC) - ethSrc.Connection().SetPortName(p1.ID()) - ethSrc.Ipv4Addresses().Add().SetName(srcDev.Name() + ".ipv4").SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) - ethSrc.Ipv6Addresses().Add().SetName(srcDev.Name() + ".ipv6").SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) - - p2 := ate.Port(t, "port2") - top.Ports().Add().SetName(p2.ID()) - dstDev := top.Devices().Add().SetName(atePort2.Name) - ethDst := dstDev.Ethernets().Add().SetName(atePort2.Name + ".eth").SetMac(atePort2.MAC) - if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { - ethDst.Vlans().Add().SetName(atePort2.Name + "vlan").SetId(1) - } - ethDst.Connection().SetPortName(p2.ID()) - ethDst.Ipv4Addresses().Add().SetName(dstDev.Name() + ".ipv4").SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) - ethDst.Ipv6Addresses().Add().SetName(dstDev.Name() + ".ipv6").SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) - - // configure vlans on ATE port2 - dstDevVlan10 := top.Devices().Add().SetName(atePort2Vlan10.Name) - ethDstVlan10 := dstDevVlan10.Ethernets().Add().SetName(atePort2Vlan10.Name + ".eth").SetMac(atePort2Vlan10.MAC) - ethDstVlan10.Connection().SetPortName(p2.ID()) - ethDstVlan10.Vlans().Add().SetName(atePort2Vlan10.Name + "vlan").SetId(10) - ethDstVlan10.Ipv4Addresses().Add().SetName(atePort2Vlan10.Name + ".ipv4").SetAddress(atePort2Vlan10.IPv4).SetGateway(dutPort2Vlan10.IPv4).SetPrefix(uint32(atePort2Vlan10.IPv4Len)) - ethDstVlan10.Ipv6Addresses().Add().SetName(atePort2Vlan10.Name + ".ipv6").SetAddress(atePort2Vlan10.IPv6).SetGateway(dutPort2Vlan10.IPv6).SetPrefix(uint32(atePort2Vlan10.IPv6Len)) - - dstDevVlan20 := top.Devices().Add().SetName(atePort2Vlan20.Name) - ethDstVlan20 := dstDevVlan20.Ethernets().Add().SetName(atePort2Vlan20.Name + ".eth").SetMac(atePort2Vlan20.MAC) - ethDstVlan20.Connection().SetPortName(p2.ID()) - ethDstVlan20.Vlans().Add().SetName(atePort2Vlan20.Name + "vlan").SetId(20) - ethDstVlan20.Ipv4Addresses().Add().SetName(atePort2Vlan20.Name + ".ipv4").SetAddress(atePort2Vlan20.IPv4).SetGateway(dutPort2Vlan20.IPv4).SetPrefix(uint32(atePort2Vlan20.IPv4Len)) - ethDstVlan20.Ipv6Addresses().Add().SetName(atePort2Vlan20.Name + ".ipv6").SetAddress(atePort2Vlan20.IPv6).SetGateway(dutPort2Vlan20.IPv6).SetPrefix(uint32(atePort2Vlan20.IPv6Len)) - - dstDevVlan30 := top.Devices().Add().SetName(atePort2Vlan30.Name) - ethDstVlan30 := dstDevVlan30.Ethernets().Add().SetName(atePort2Vlan30.Name + ".eth").SetMac(atePort2Vlan30.MAC) - ethDstVlan30.Connection().SetPortName(p2.ID()) - ethDstVlan30.Vlans().Add().SetName(atePort2Vlan30.Name + "vlan").SetId(30) - ethDstVlan30.Ipv4Addresses().Add().SetName(atePort2Vlan30.Name + ".ipv4").SetAddress(atePort2Vlan30.IPv4).SetGateway(dutPort2Vlan30.IPv4).SetPrefix(uint32(atePort2Vlan30.IPv4Len)) - ethDstVlan30.Ipv6Addresses().Add().SetName(atePort2Vlan30.Name + ".ipv6").SetAddress(atePort2Vlan30.IPv6).SetGateway(dutPort2Vlan30.IPv6).SetPrefix(uint32(atePort2Vlan30.IPv6Len)) - - return top -} - -// configNetworkInstance creates VRFs and subinterfaces and then applies VRFs on the subinterfaces. -func configNetworkInstance(t *testing.T, dut *ondatra.DUTDevice, vrfname string, intfname string, subint uint32, vlanID uint16) { - // create empty subinterface - si := &oc.Interface_Subinterface{} - si.Index = ygot.Uint32(subint) - if deviations.DeprecatedVlanID(dut) { - si.GetOrCreateVlan().VlanId = oc.UnionUint16(vlanID) - } else { - si.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(vlanID) - } - s4 := si.GetOrCreateIpv4() - if deviations.InterfaceEnabled(dut) { - s4.Enabled = ygot.Bool(true) - } - gnmi.Replace(t, dut, gnmi.OC().Interface(intfname).Subinterface(subint).Config(), si) - - // create vrf and apply on subinterface - v := &oc.NetworkInstance{ - Name: ygot.String(vrfname), - Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF, - } - vi := v.GetOrCreateInterface(intfname + "." + strconv.Itoa(int(subint))) - vi.Interface = ygot.String(intfname) - vi.Subinterface = ygot.Uint32(subint) - gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrfname).Config(), v) -} - -// getSubInterface returns a subinterface configuration populated with IP addresses and VLAN ID. -func getSubInterface(dutPort *attrs.Attributes, index uint32, vlanID uint16, dut *ondatra.DUTDevice) *oc.Interface_Subinterface { - s := &oc.Interface_Subinterface{} - // unshut sub/interface - if deviations.InterfaceEnabled(dut) { - s.Enabled = ygot.Bool(true) - } - s.Index = ygot.Uint32(index) - s4 := s.GetOrCreateIpv4() - if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { - s4.Enabled = ygot.Bool(true) - } - a := s4.GetOrCreateAddress(dutPort.IPv4) - a.PrefixLength = ygot.Uint8(dutPort.IPv4Len) - s6 := s.GetOrCreateIpv6() - if deviations.InterfaceEnabled(dut) { - s6.Enabled = ygot.Bool(true) - } - a6 := s6.GetOrCreateAddress(dutPort.IPv6) - a6.PrefixLength = ygot.Uint8(dutPort.IPv6Len) - if index != 0 { - if deviations.DeprecatedVlanID(dut) { - s.GetOrCreateVlan().VlanId = oc.UnionUint16(vlanID) - } else { - s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(vlanID) - } - } - return s -} - -// configInterfaceDUT configures the interface with the Addrs. -func configInterfaceDUT(i *oc.Interface, dutPort *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { - if deviations.InterfaceEnabled(dut) { - i.Enabled = ygot.Bool(true) - } - i.Description = ygot.String(dutPort.Desc) - i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd - i.AppendSubinterface(getSubInterface(dutPort, 0, 0, dut)) - return i -} - -// configureDUT configures the base configuration on the DUT. -func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { - d := gnmi.OC() - - p1 := dut.Port(t, "port1") - i1 := &oc.Interface{Name: ygot.String(p1.Name())} - gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut)) - - p2 := dut.Port(t, "port2") - i2 := &oc.Interface{Name: ygot.String(p2.Name())} - gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutPort2, dut)) - if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { - i3 := &oc.Interface{Name: ygot.String(p2.Name())} - s := i3.GetOrCreateSubinterface(0) - s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(1) - gnmi.Update(t, dut, d.Interface(p2.Name()).Config(), i3) - } - - if deviations.ExplicitPortSpeed(dut) { - fptest.SetPortSpeed(t, p1) - fptest.SetPortSpeed(t, p2) - } - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) - } - - outpath := d.Interface(p2.Name()) - // create VRFs and VRF enabled subinterfaces - configNetworkInstance(t, dut, "VRF10", p2.Name(), uint32(1), 10) - - // configure IP addresses on subinterfaces - gnmi.Update(t, dut, outpath.Subinterface(1).Config(), getSubInterface(&dutPort2Vlan10, 1, 10, dut)) - - configNetworkInstance(t, dut, "VRF20", p2.Name(), uint32(2), 20) - gnmi.Update(t, dut, outpath.Subinterface(2).Config(), getSubInterface(&dutPort2Vlan20, 2, 20, dut)) - - configNetworkInstance(t, dut, "VRF30", p2.Name(), uint32(3), 30) - gnmi.Update(t, dut, outpath.Subinterface(3).Config(), getSubInterface(&dutPort2Vlan30, 3, 30, dut)) -} - -// getIPinIPFlow returns an IPv4inIPv4 *ondatra.Flow with provided DSCP value for a given set of endpoints. -func getIPinIPFlow(args *testArgs, src attrs.Attributes, dst attrs.Attributes, flowName string, dscp uint32) gosnappi.Flow { - - flow := gosnappi.NewFlow().SetName(flowName) - flow.Metrics().SetEnable(true) - flow.TxRx().Device().SetTxNames([]string{src.Name + "." + args.iptype}).SetRxNames([]string{dst.Name + "." + args.iptype}) - ethHeader := flow.Packet().Add().Ethernet() - ethHeader.Src().SetValue(src.MAC) - outerIPHeader := flow.Packet().Add().Ipv4() - outerIPHeader.Src().SetValue(src.IPv4) - outerIPHeader.Dst().SetValue(dst.IPv4) - outerIPHeader.Priority().Dscp().Phb().SetValue(dscp) - innerIPHeader := flow.Packet().Add().Ipv4() - innerIPHeader.Src().SetValue("198.51.100.1") - innerIPHeader.Dst().Increment().SetStart("203.0.113.1").SetStep("0.0.0.1").SetCount(10000) - - flow.Size().SetFixed(1024) - flow.Rate().SetPps(100) - flow.Duration().FixedPackets().SetPackets(100) - - return flow -} - -// testTrafficFlows verifies traffic for one or more flows. -func testTrafficFlows(t *testing.T, args *testArgs, expectPass bool, flows ...gosnappi.Flow) { - - args.top.Flows().Clear() - for _, flow := range flows { - args.top.Flows().Append(flow) - } - args.ate.OTG().PushConfig(t, args.top) - args.ate.OTG().StartProtocols(t) - otgutils.WaitForARP(t, args.ate.OTG(), args.top, "IPv4") - - t.Logf("*** Starting traffic ...") - args.ate.OTG().StartTraffic(t) - time.Sleep(trafficDuration) - t.Logf("*** Stop traffic ...") - args.ate.OTG().StopTraffic(t) - - if expectPass { - t.Log("Expecting traffic to pass for the flows") - } else { - t.Log("Expecting traffic to fail for the flows") - } - - top := args.ate.OTG().GetConfig(t) - otgutils.LogFlowMetrics(t, args.ate.OTG(), top) - for _, flow := range flows { - t.Run(flow.Name(), func(t *testing.T) { - t.Logf("*** Verifying %v traffic on OTG ... ", flow.Name()) - outPkts := float32(gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State())) - inPkts := float32(gnmi.Get(t, args.ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State())) - - if outPkts == 0 { - t.Fatalf("OutPkts == 0, want >0.") - } - - lossPct := (outPkts - inPkts) * 100 / outPkts - - // log stats - t.Log("Flow LossPct: ", lossPct) - t.Log("Flow InPkts : ", inPkts) - t.Log("Flow OutPkts : ", outPkts) - - if (expectPass == true) && (lossPct == 0) { - t.Logf("Traffic for %v flow is passing as expected", flow.Name()) - } else if (expectPass == false) && (lossPct == 100) { - t.Logf("Traffic for %v flow is failing as expected", flow.Name()) - } else { - t.Fatalf("Traffic is not working as expected for flow: %v.", flow.Name()) - } - }) - } -} - -// getL3PBRRule returns an IPv4 or IPv6 policy-forwarding rule configuration populated with protocol and/or DSCPset information. -func getL3PBRRule(args *testArgs, networkInstance string, index uint32, dscpset []uint8) *oc.NetworkInstance_PolicyForwarding_Policy_Rule { - r := oc.NetworkInstance_PolicyForwarding_Policy_Rule{} - r.SequenceId = ygot.Uint32(index) - r.Action = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action{NetworkInstance: ygot.String(networkInstance)} - if args.iptype == "ipv4" { - r.Ipv4 = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Ipv4{ - Protocol: args.protocol, - } - if len(dscpset) > 0 { - r.Ipv4.DscpSet = dscpset - } - } else if args.iptype == "ipv6" { - r.Ipv6 = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Ipv6{ - Protocol: args.protocol, - } - if len(dscpset) > 0 { - r.Ipv6.DscpSet = dscpset - } - } else { - return nil - } - return &r - -} - -// getPBRPolicyForwarding returns pointer to policy-forwarding populated with pbr policy and rules -func getPBRPolicyForwarding(args *testArgs, rules ...*oc.NetworkInstance_PolicyForwarding_Policy_Rule) *oc.NetworkInstance_PolicyForwarding { - pf := oc.NetworkInstance_PolicyForwarding{} - p := pf.GetOrCreatePolicy(args.policyName) - p.Type = oc.Policy_Type_VRF_SELECTION_POLICY - for _, rule := range rules { - p.AppendRule(rule) - } - return &pf -} - -func TestPBR(t *testing.T) { - t.Logf("Description: Test RT3.2 with multiple DSCP, IPinIP protocol rule based VRF selection") - dut := ondatra.DUT(t, "dut") - - // configure DUT - configureDUT(t, dut) - - // Configure ATE - ate := ondatra.ATE(t, "ate") - top := configureATE(t, ate, dut) - ate.OTG().PushConfig(t, top) - ate.OTG().StartProtocols(t) - - args := &testArgs{ - dut: dut, - ate: ate, - top: top, - policyName: "L3", - iptype: "ipv4", - protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, - } - - // dut ingress interface - port1 := dut.Port(t, "port1") - - cases := []struct { - name string - desc string - policy *oc.NetworkInstance_PolicyForwarding - passingFlows []gosnappi.Flow - failingFlows []gosnappi.Flow - rejectable bool - }{ - { - name: "RT3.2 Case1", - desc: "Ensure matching IPinIP with DSCP (10 - VRF10, 20- VRF20, 30-VRF30) traffic reaches appropriate VLAN.", - policy: getPBRPolicyForwarding(args, - getL3PBRRule(args, "VRF10", 1, []uint8{10}), - getL3PBRRule(args, "VRF20", 2, []uint8{20}), - getL3PBRRule(args, "VRF30", 3, []uint8{30})), - // use IPinIP DSCP10, DSCP20, DSCP30 flows for VLAN10, VLAN20 and VLAN30 respectively. - passingFlows: []gosnappi.Flow{ - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd10", 10), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd20", 20), - getIPinIPFlow(args, atePort1, atePort2Vlan30, "ipinipd30", 30)}, - }, - { - name: "RT3.2 Case2", - desc: "Ensure matching IPinIP with DSCP (10-12 - VRF10, 20-22- VRF20, 30-32-VRF30) traffic reaches appropriate VLAN.", - policy: getPBRPolicyForwarding(args, - getL3PBRRule(args, "VRF10", 1, []uint8{10, 11, 12}), - getL3PBRRule(args, "VRF20", 2, []uint8{20, 21, 22}), - getL3PBRRule(args, "VRF30", 3, []uint8{30, 31, 32})), - // use IPinIP flows with DSCP10-12 for VLAN10, DSCP20-22 for VLAN20, DSCP30-32 for VLAN30. - passingFlows: []gosnappi.Flow{ - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd10", 10), - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd11", 11), - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd12", 12), - - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd20", 20), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd21", 21), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd22", 22), - - getIPinIPFlow(args, atePort1, atePort2Vlan30, "ipinipd30", 30), - getIPinIPFlow(args, atePort1, atePort2Vlan30, "ipinipd31", 31), - getIPinIPFlow(args, atePort1, atePort2Vlan30, "ipinipd32", 32)}, - }, - { - name: "RT3.2 Case3", - desc: "Ensure first matching of IPinIP with DSCP (10-12 - VRF10, 10-12 - VRF20) rule takes precedence.", - policy: getPBRPolicyForwarding(args, - getL3PBRRule(args, "VRF10", 1, []uint8{10, 11, 12}), - getL3PBRRule(args, "VRF20", 2, []uint8{10, 11, 12})), - // use IPinIP DSCP10-12 flows for VLAN10 as well as VLAN20. - passingFlows: []gosnappi.Flow{ - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd10", 10), - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd11", 11), - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd12", 12)}, - failingFlows: []gosnappi.Flow{ - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd10v20", 10), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd11v20", 11), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd12v20", 12)}, - rejectable: true, - }, - { - name: "RT3.2 Case4", - desc: "Ensure matching IPinIP to VRF10, IPinIP with DSCP20 to VRF20 causes unspecified DSCP IPinIP traffic to match VRF10.", - policy: getPBRPolicyForwarding(args, - getL3PBRRule(args, "VRF10", 1, []uint8{}), - getL3PBRRule(args, "VRF20", 2, []uint8{20})), - // use IPinIP DSCP10-12 flows to match IPinIP to VRF10 - // use IPinIP DSCP20 flow to match to VRF20 - // use IPinIP DSCP10-12 flows to match to VRF20 to show they fail for VRF20 - passingFlows: []gosnappi.Flow{ - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd10", 10), - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd11", 11), - getIPinIPFlow(args, atePort1, atePort2Vlan10, "ipinipd12", 12)}, - failingFlows: []gosnappi.Flow{ - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd10v20", 10), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd11v20", 11), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd12v20", 12), - getIPinIPFlow(args, atePort1, atePort2Vlan20, "ipinipd20", 20)}, - }, - } - for _, tc := range cases { - t.Run(tc.name, func(t *testing.T) { - t.Log(tc.desc) - pfpath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() - - //configure pbr policy-forwarding - fptest.ConfigureDefaultNetworkInstance(t, dut) - - errMsg := testt.CaptureFatal(t, func(t testing.TB) { - gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), tc.policy) - }) - if errMsg != nil { - if tc.rejectable { - t.Skipf("Skipping test case %q, PolicyForwarding config was rejected with an error: %s", tc.name, *errMsg) - } - t.Fatalf("PolicyForwarding config update failed: %v", *errMsg) - } - // defer cleaning policy-forwarding - defer gnmi.Delete(t, args.dut, pfpath.Config()) - - // apply pbr policy on ingress interface - p1 := port1.Name() - d := &oc.Root{} - interfaceID := p1 - if deviations.InterfaceRefInterfaceIDFormat(dut) { - interfaceID = p1 + ".0" - } - pfIntf := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreatePolicyForwarding().GetOrCreateInterface(interfaceID) - pfIntfConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Interface(interfaceID) - pfIntf.GetOrCreateInterfaceRef().Interface = ygot.String(p1) - pfIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) - if deviations.InterfaceRefConfigUnsupported(dut) { - pfIntf.InterfaceRef = nil - } - pfIntf.SetApplyVrfSelectionPolicy(args.policyName) - gnmi.Update(t, dut, pfIntfConfPath.Config(), pfIntf) - - // defer deletion of policy from interface - defer gnmi.Delete(t, args.dut, pfIntfConfPath.Config()) - // traffic should pass - testTrafficFlows(t, args, true, tc.passingFlows...) - - if len(tc.failingFlows) > 0 { - // traffic should fail - testTrafficFlows(t, args, false, tc.failingFlows...) - } - }) - } -} diff --git a/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/README.md b/feature/policy_forwarding/vrf_selection/otg_tests/base_vrf_selection/README.md similarity index 100% rename from feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/README.md rename to feature/policy_forwarding/vrf_selection/otg_tests/base_vrf_selection/README.md diff --git a/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go b/feature/policy_forwarding/vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go similarity index 100% rename from feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go rename to feature/policy_forwarding/vrf_selection/otg_tests/base_vrf_selection/base_vrf_selection_test.go diff --git a/feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto b/feature/policy_forwarding/vrf_selection/otg_tests/base_vrf_selection/metadata.textproto similarity index 100% rename from feature/policy_forwarding/policy_vrf_selection/otg_tests/base_vrf_selection/metadata.textproto rename to feature/policy_forwarding/vrf_selection/otg_tests/base_vrf_selection/metadata.textproto diff --git a/feature/policy_forwarding/vrf_selection/otg_tests/multiple_vrfs_and_gue_decap/README.md b/feature/policy_forwarding/vrf_selection/otg_tests/multiple_vrfs_and_gue_decap/README.md new file mode 100644 index 00000000000..d899d7e617f --- /dev/null +++ b/feature/policy_forwarding/vrf_selection/otg_tests/multiple_vrfs_and_gue_decap/README.md @@ -0,0 +1,391 @@ +# PF-2.3: Multiple VRFs and GUE DECAP in Default VRF + +## Summary +This test ensures NOS is able to host multiple VRFs and at the same time perform GUE tunnel DECAP in the default VRF + +## Procedure +Test environment setup + +### Topology +Create the following connections: +```mermaid +graph LR; + +A[ATE:Port1] <--IBGP(ASN100)--> B[Port1:DUT:Port2];B <--EBGP(ASN100:ASN200)--> C[Port2:ATE]; +``` + +## Implementation Details + +### Baseline DUT configuration + - DUT has 2 VRFs, Default and Non-Default. + - DUT:Port1 and DUT:Port2 are in the Default VRF. + - BGP sessions between ATE:Port1 <> DUT:Port1 and ATE:PORT2 <> DUT:PORT2 are in Default VRF. + - Ensure leaking routes from the default to the non-default VRFs so prefixes learnt over IBGP and EBGP are available in the tables of both VRFs.There are no other routes learned into non-Default VRF. + - Ensure IPv4Prefix12/28 is configured on the Loopback0 interface of DUT which is also in the Default VRF + - Loopback0:IPv4Prefix12/28 is used as target address for GUE decapsulation on DUT + - Ensure the DUT has accurate QOS classifier configured on DUT:PORT1 and and DUT:PORT2 ports, with DSCP and traffic-class mapping called out in section "Traffic-Class to DSCP mapping". + +### Traffic-Class to DSCP mapping + +| **Traffic-Class** | **DSCP** | +| :------- | :---------- | +| **be1** | 000000 | +| **af1** | 001000 | +| **af2** | 010000 | +| **af3** | 011000 | +| **af4** | 100000 | + +### Baseline ATE configuration + - DUT has 2 VRFs, Default and Non-Default(say B2_VRF). + - Establish BGP sessions as shown in the DUT configuration section + - ATE1 advertises routes shown in "ATE Route Advertisements" section + - ATE2 advertises routes shown in "ATE Route Advertisements" section + - Configure ATE with Flow-types mentioned in "Packet types and Flow-types" + +### ATE Route Advertisements: + +| **Source ATE Nodes** | **Advertisement Type** | **Prefixes** | **Via BGP Sessions** | **Notes** | +| -------------- | ---------------------- | ------------------ | ---------------------------------------| -----------------| +| ATE1 | IBGP | IPv4prefix1-5/24 | IPv4 DUT <--> ATE1 | | +| ATE1 | IBGP | IPv6prefix1-5/64 | IPv6 DUT <--> ATE1 | | +| ATE2 | EBGP | IPv4prefix6-10/24 | IPv4 DUT <--> ATE2 | | +| ATE2 | EBGP | IPv6prefix6-10/64 | IPv6 DUT <--> ATE2 | | +| DUT | EBGP | IPv4Prefix12/28 | IPv4 DUT <--> ATE2 | DUT-DECAP-Address | + + +#### Packet types and Flow-types + +| Flow-type | Packet# | Layer | Protocol | Source Address | Destination Address | Source Port | Destination Port | DSCP | Notes | +| :------- | :------- | :---------- | :---------------- | :------------------ | :------------------ | :------------------ | :------------------------ | :------------------|:------------------------------------------ | +| **1to6v4_encapped**| **1** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv4\|UDP | IPv4Prefix1/24 | IPv4Prefix6/24 | 14 | 15 | BE1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | BE1 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **2to7v4_encapped** | **2** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv4\|UDP | IPv4Prefix2/24 | IPv4Prefix7/24 | 14 | 15 | AF1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF1 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **3to8v4_encapped** | **3** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv4\|UDP | IPv4Prefix3/24 | IPv4Prefix8/24 | 14 | 15 | AF2 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF2 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **4to9v4_encapped** | **4** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv4\|UDP | IPv4Prefix4/24 | IPv4Prefix9/24 | 14 | 15 | AF3 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF3 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **5to10v4_encapped** |**5** | **Overall** | **Payload o IPv4\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv4\|UDP | IPv4Prefix5/24 | IPv4Prefix10/24 | 14 | 15 | AF4 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF4 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **1to6v6_encapped** |**6** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | +| | | Inner | IPv6\|UDP | IPv6Prefix1/24 | IPv6Prefix6/24 | 14 | 15 | BE1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | BE1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **2to7v6_encapped** | **7** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv6\|UDP | IPv6Prefix2/24 | IPv6Prefix7/24 | 14 | 15 | AF1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF1 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **3to8v6_encapped** | **8** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv6\|UDP | IPv6Prefix3/24 | IPv6Prefix8/24 | 14 | 15 | AF2 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF2 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **4to9v6_encapped** | **9** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv6\|UDP | IPv6Prefix4/24 | IPv6Prefix9/24 | 14 | 15 | AF3 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF3 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **5to10v6_encapped** | **10** | **Overall** | **Payload o IPv6\|UDP o IPv4\|UDP(GUE v1)** | | | | | | +| | | Inner | IPv6\|UDP | IPv6Prefix5/24 | IPv6Prefix10/24 | 14 | 15 | AF4 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| | | Outer | IPv4\|UDP(GUE v1) | ATE1-port IPv4 addr | DUT-DECAP-Address | 5996 | 6080 | AF4 | Src Port: Any unassigned UDP port; GUE v1 encapsulation | +| **1to6v4** | **11** | **Overall** | **Payload o IPv4\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv4Prefix1/24 | IPv4Prefix6/24 | 14 | 15 | BE1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **2to7v4** | **12** | **Overall** | **Payload o IPv4\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv4Prefix2/24 | IPv4Prefix7/24 | 14 | 15 | AF1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **3to8v4** | **13** | **Overall** | **Payload o IPv4\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv4Prefix3/24 | IPv4Prefix8/24 | 14 | 15 | AF2 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **4to9v4** | **14** | **Overall** | **Payload o IPv4\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv4Prefix4/24 | IPv4Prefix9/24 | 14 | 15 | AF3 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **5to10v4** | **15** | **Overall** | **Payload o IPv4\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv4Prefix5/24 | IPv4Prefix10/24 | 14 | 15 | AF4 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **1to6v6** | **16** | **Overall** | **Payload o IPv6\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv6Prefix1/24 | IPv6Prefix6/24 | 14 | 15 | BE1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **2to7v6** | **17** | **Overall** | **Payload o IPv6\|UDP** | | | | | | | +| | | Payload header | IPv6\|UDP | IPv6Prefix2/24 | IPv6Prefix7/24 | 14 | 15 | AF1 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **3to8v6** | **18** | **Overall** | **Payload o IPv6\|UDP** | | | | | | +| | | Payload header | IPv4\|UDP | IPv6Prefix3/24 | IPv6Prefix8/24 | 14 | 15 | AF2 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **4to9v6** | **19** | **Overall** | **Payload o IPv6\|UDP** | | | | | | | +| | | Payload header | IPv6\|UDP | IPv6Prefix4/24 | IPv6Prefix9/24 | 14 | 15 | AF3 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | +| **5to10v6** | **20** | **Overall** | **Payload o IPv6\|UDP** | | | | | | | +| | | Payload header | IPv6\|UDP | IPv6Prefix5/24 | IPv6Prefix10/24 | 14 | 15 | AF4 | Src Port: Any unassigned UDP port; Dst Port: Any App/unassigned UDP port | + +## Procedure + +### PF-2.3.1: [Baseline] Traffic flow between ATE:Port1 and ATE:Port2 via DUT's Default VRF + * Start above flow-type 1to6v4 - 5to10v4 and 1to6v6 - 5to10v6 between ATE:Port1 and ATE:Port2. + * Ensure no packet loss is observed


+ +### PF-2.3.2: BE1 traffic from ATE:Port1 to ATE:Port2 simulated to be GUE Encaped and sent to the DUT's Default VRF by ATE:Port2 + * ATE:Port1 sends flow-type 1to6v4_encapped and 1to6v6_encapped + * DUT will perform the decapsulation in Default VRF + * ATE:Port1 continues to send the following IPv4 and IPv6 flows + * 2to7v4 - 5to10v4 + * 2to7v4 - 5to10v6 + * DUT receives the tunneled traffic for flow-type 1to6v4_encapped and 1to6v6_encapped, decaps it, does a LPM lookup on the destination prefix (IPv4Prefix6 and IPv6Prefix6) and routes it to ATE:Port2 via DUT:Port2 + * All the traffic flows MUST show no packet loss. + * Streamed data on the number of packets decaped by the Tunnel endpoint "IPv4Prefix12" must match the number of tunnel encaped packets sent by ATE:Port1 for 1to6v4_encapped and 1to6v6_encapped flows .


+ +### PF-2.3.3: BE1 and AF1 traffic from ATE:Port1 to ATE:Port2 simulated to be GUE Encaped and sent to the DUT's Default VRF by ATE:Port2 + * ATE:Port1 sends flow-types 1to6v4_encapped - 2to7v4_encapped, 1to6v6_encapped - 2to7v6_encapped + * ATE:Port1 continues to send the following IPv4 and IPv6 flows + * 3to8v4 - 5to10v4 + * 3to8v4 - 5to10v6 + * DUT will perform the decapsulation in Default VRF + * DUT receives the tunneled traffic for flow-type 1to6v4_encapped - 2to74_encapped, 1to6v6_encapped - 2to7v6_encapped, decaps it, does a LPM lookup on the destination prefix (IPv4Prefix6, IPv4Prefix7, IPv6Prefix6, IPv6Prefix7) and routes it to ATE:Port2 via DUT:Port2 + * All the traffic flows MUST show no packet loss. + * Streamed data on the number of packets decaped by the Tunnel endpoint "IPv4Prefix12" must match the number of tunnel encaped packets sent by ATE:Port1 for 1to6v4_encapped - 2to7v4_encapped, 1to6v6_encapped - 2to7v6_encapped flows .


+ + +### PF-2.3.4: BE1, AF1 and AF2 traffic from ATE:Port1 to ATE:Port2 simulated to be GUE Encaped and sent to the DUT's Default VRF by ATE:Port2 + * ATE:Port1 sends flow-types 1to6v4_encapped - 3to8v4_encapped, 1to6v6_encapped - 3to8v6_encapped + * ATE:Port1 continues to send the following IPv4 and IPv6 flows + * 4to9v4 - 5to10v4 + * 4to9v4 - 5to10v6 + * DUT will perform the decapsulation in Default VRF + * DUT receives the tunneled traffic for flow-types 1to6v4_encapped - 3to8v4_encapped, 1to6v6_encapped - 3to8v6_encapped, decaps it, does a LPM lookup on the destination prefix (IPv4Prefix6 - IPv4Prefix8, IPv6Prefix6 - IPv6Prefix8) and routes it to ATE:Port2 via DUT:Port2 + * All the traffic flows MUST show no packet loss. + * Streamed data on the number of packets decaped by the Tunnel endpoint "IPv4Prefix12" must match the number of tunnel encaped packets sent by ATE:Port1 for 1to6v4_encapped - 3to8v4_encapped, 1to6v6_encapped - 3to8v6_encapped .


+ +### PF-2.3.5: BE1, AF1, AF2 and AF3 traffic from ATE:Port1 to ATE:Port2 simulated to be GUE Encaped and sent to the DUT's Default VRF by ATE:Port2 + * ATE:Port1 sends flow-types 1to6v4_encapped - 4to9v4_encapped, 1to6v6_encapped - 4to9v6_encapped + * ATE:Port1 continues to send the following IPv4 and IPv6 flows + * 5to10v4 + * 5to10v6 + * DUT will perform the decapsulation in Default VRF + * DUT receives the tunneled traffic for flow-types 1to6v4_encapped - 4to9v4_encapped, 1to6v6_encapped - 4to9v6_encapped, decaps it, does a LPM lookup on the destination prefix (IPv4Prefix6 - IPv4Prefix9, IPv6Prefix6 - IPv6Prefix9) and routes it to ATE:Port2 via DUT:Port2 + * All the traffic flows MUST show no packet loss. + * Streamed data on the number of packets decaped by the Tunnel endpoint "IPv4Prefix12" must match the number of tunnel encaped packets sent by ATE:Port1 for 1to6v4_encapped - 4to9v4_encapped, 1to6v6_encapped - 4to9v6_encapped .


+ +### PF-2.3.6: BE1, AF1, AF2, AF3 and AF4 traffic from ATE:Port1 to ATE:Port2 simulated to be GUE Encaped and sent to the DUT's Default VRF by ATE:Port2 + * ATE:Port1 sends flow-types 1to6v4_encapped - 5to10v4_encapped, 1to6v6_encapped - 5to10v6_encapped + * DUT will perform the decapsulation in Default VRF + * DUT receives the tunneled traffic for flow-types 1to6v4_encapped - 5to10v4_encapped, 1to6v6_encapped - 5to10v6_encapped, decaps it, does a LPM lookup on the destination prefix (IPv4Prefix6 - IPv4Prefix10, IPv6Prefix6 - IPv6Prefix10) and routes it to ATE:Port2 via DUT:Port2 + * All the traffic flows MUST show no packet loss. + * Streamed data on the number of packets decaped by the Tunnel endpoint "IPv4Prefix12" must match the number of tunnel encaped packets sent by ATE:Port1 for 1to6v4_encapped - 5to10v4_encapped, 1to6v6_encapped - 5to10v6_encapped .


+ + +## Canonical OC +TODO: decap policy to be updated by https://github.com/openconfig/public/pull/1288 + +```json +{ + "defined-sets": { + "ipv4-prefix-sets": { + "ipv4-prefix-set": [ + { + "config": { + "name": "dst_prefix", + "prefix": [ + "192.168.1.1/32" + ] + }, + "name": "dst_prefix" + } + ] + } + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "B2_VRF" + }, + "inter-instance-policies": { + "apply-policy": { + "config": { + "import-policy": [ + "BGP_EXPORT_B2_VRF" + ] + } + } + }, + "name": "B2_VRF" + }, + { + "config": { + "name": "DEFAULT" + }, + "inter-instance-policies": { + "apply-policy": { + "config": { + "export-policy": [ + "BGP_EXPORT_B2_VRF" + ] + } + } + }, + "name": "DEFAULT", + "policy-forwarding": { + "policies": { + "policy": [ + { + "config": { + "policy-id": "decap-policy" + }, + "policy-id": "decap-policy", + "rules": { + "rule": [ + { + "action": { + "config": { + "decapsulate-gue": true + } + }, + "config": { + "sequence-id": 1 + }, + "ipv4": { + "config": { + "destination-address-prefix-set": "dst_prefix", + "protocol": "IP_UDP" + } + }, + "sequence-id": 1, + "transport": { + "config": { + "destination-port": 6080 + } + } + } + ] + } + } + ] + } + } + } + ] + }, + "routing-policy": { + "defined-sets": { + "prefix-sets": { + "prefix-set": [ + { + "config": { + "name": "IPv4Prefixes_IBGP" + }, + "name": "IPv4Prefixes_IBGP", + "prefixes": { + "prefix": [ + { + "config": { + "ip-prefix": "0.0.0.0/0", + "masklength-range": "24..24" + }, + "ip-prefix": "0.0.0.0/0", + "masklength-range": "24..24" + } + ] + } + }, + { + "config": { + "name": "IPv6Prefixes_IBGP" + }, + "name": "IPv6Prefixes_IBGP", + "prefixes": { + "prefix": [ + { + "config": { + "ip-prefix": "0000:0000:0000:0000:0000:0000:0000:0000/0", + "masklength-range": "64..64" + }, + "ip-prefix": "0000:0000:0000:0000:0000:0000:0000:0000/0", + "masklength-range": "64..64" + } + ] + } + } + ] + } + }, + "policy-definitions": { + "policy-definition": [ + { + "config": { + "name": "BGP_EXPORT_B2_VRF" + }, + "name": "BGP_EXPORT_B2_VRF", + "statements": { + "statement": [ + { + "actions": { + "config": { + "policy-result": "ACCEPT_ROUTE" + } + }, + "conditions": { + "match-prefix-set": { + "config": { + "prefix-set": "IPv4Prefixes_IBGP" + } + } + }, + "config": { + "name": "IPv4Prefix_IBGP_EXPORT" + }, + "name": "IPv4Prefix_IBGP_EXPORT" + }, + { + "conditions": { + "match-prefix-set": { + "config": { + "prefix-set": "IPv6Prefixes_IBGP" + } + } + }, + "config": { + "name": "IPv6Prefix_IBGP_EXPORT" + }, + "name": "IPv6Prefix_IBGP_EXPORT" + } + ] + } + } + ] + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + +/network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/ipv4/config/destination-address-prefix-set: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/ipv4/config/protocol: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/config/destination-port: +/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-gue: +/network-instances/network-instance/inter-instance-policies/apply-policy/config/export-policy: +/network-instances/network-instance/inter-instance-policies/apply-policy/config/import-policy: +/routing-policy/policy-definitions/policy-definition/config/name: +/routing-policy/policy-definitions/policy-definition/statements/statement/config/name: +/routing-policy/policy-definitions/policy-definition/statements/statement/name/actions/policy-result: +/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set/config/community-set: +/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set/config/match-set-options: +/routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result: +/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set: +/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/masklength-range: +/routing-policy/defined-sets/prefix-sets/prefix-set/prefixes/prefix/config/ip-prefix: +/routing-policy/defined-sets/prefix-sets/prefix-set/config/masklength-range: +/routing-policy/defined-sets/prefix-sets/prefix-set/config/ip-prefix: +/routing-policy/defined-sets/prefix-sets/prefix-set/config/prefix-set-name: + +# telemetry +/interfaces/interface/state/counters/out-pkts: +/interfaces/interface/state/counters/out-unicast-pkts: +/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/state/dropped-bytes-aggregate: +/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/state/dropped-aggregate: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` +## Required DUT platform +* Specify the minimum DUT-type: + * FFF - fixed form factor diff --git a/feature/policy_forwarding/vrf_selection/otg_tests/multiple_vrfs_and_gue_decap/metadata.textproto b/feature/policy_forwarding/vrf_selection/otg_tests/multiple_vrfs_and_gue_decap/metadata.textproto new file mode 100644 index 00000000000..d02ee67f758 --- /dev/null +++ b/feature/policy_forwarding/vrf_selection/otg_tests/multiple_vrfs_and_gue_decap/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "e0814277-013d-4d59-b6a0-231949992edc" +plan_id: "PF-2.3" +description: "Multiple VRFs and GUE DECAP in Default VRF" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md b/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md similarity index 76% rename from feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md rename to feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md index 944b0aa33ff..d4cbafeaddd 100644 --- a/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md +++ b/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/README.md @@ -6,12 +6,22 @@ Ensure that multiple protocol and dscp based VRF selection rules are matched cor ## Procedure -Configure DUT with 1 input interface connected to ATE port-1, and a second interface (output) connected to ATE port-2 with VLAN-based subinterfaces, with the following assignments: +Configure DUT with 1 input interface connected to ATE port-1, and a second +interface (output) connected to ATE port-2 with VLAN-based subinterfaces, +with the following assignments: * network-instance “10” corresponding to VLAN 10, default route via VLAN 10 subinterface. * network-instance “20” corresponding to VLAN 20, default route via VLAN 20 subinterface. * network-instance “30” corresponding to VLAN 30, default route via VLAN 30 subinterface. +Connect DUT port-3 input interface connected to ATE port-3, and a second +interface (output) connected to ATE port-4 with VLAN-based subinterfaces, +with the following assignments: + +* network-instance “40” corresponding to VLAN 40, default route via VLAN 40 subinterface. +* network-instance “50” corresponding to VLAN 50, default route via VLAN 50 subinterface. +* network-instance “60” corresponding to VLAN 60, default route via VLAN 60 subinterface. + Configure DUT with the following rules and determine measurement: ### Case #1: @@ -50,6 +60,14 @@ It's ok that some NOS does not support this config (duplicated matching conditio Ensure that unspecified fields are wildcard and IPinIP packets are only received at VLAN 10 subinterface. +### Case #5: + +* Rules: + * Protocol IPinIP, DSCP 10 to network-instance 10 + * Protocol IPinIP, DSCP 40 to network-instance 40 + +Ensure that the dut is able to evaluate more than one vrf selection policies. + ## OpenConfig Path and RPC Coverage ```yaml paths: diff --git a/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto b/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto similarity index 96% rename from feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto rename to feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto index 6a7925547a4..fa3a1630fbd 100644 --- a/feature/policy_forwarding/policy_vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto +++ b/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/metadata.textproto @@ -4,7 +4,7 @@ uuid: "d54b1029-6f8e-4dc7-a50d-99d6e4f59474" plan_id: "RT-3.2" description: "Multiple Rules for VRF Selection" -testbed: TESTBED_DUT_ATE_2LINKS +testbed: TESTBED_DUT_ATE_4LINKS platform_exceptions: { platform: { vendor: CISCO diff --git a/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go b/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go new file mode 100644 index 00000000000..998cf517ffc --- /dev/null +++ b/feature/policy_forwarding/vrf_selection/otg_tests/protocol_dscp_rules_for_vrf_selection_test/protocol_dscp_rules_for_vrf_selection_test.go @@ -0,0 +1,816 @@ +/* + Copyright 2022 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 + + https://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 policy_based_vrf_selection_test + +import ( + "strconv" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/testt" + "github.com/openconfig/ygot/ygot" +) + +const ( + trafficDuration = 1 * time.Minute + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 +) + +// testArgs holds the objects needed by a test case. +type testArgs struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + top gosnappi.Config + iptype string + protocol oc.E_PacketMatchTypes_IP_PROTOCOL +} + +var ( + dutPort1 = attrs.Attributes{ + Desc: "dutPort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:1", + IPv6Len: ipv6PrefixLen, + } + + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:2", + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = attrs.Attributes{ + Desc: "dutPort2", + MAC: "01:00:01:01:01:01", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:5", + IPv6Len: ipv6PrefixLen, + } + + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "00:12:01:00:00:01", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:6", + IPv6Len: ipv6PrefixLen, + } + + dutPort2Vlan10 = attrs.Attributes{ + Desc: "dutPort2Vlan10", + MAC: "01:00:01:01:01:01", + IPv4: "192.0.2.9", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:9", + IPv6Len: ipv6PrefixLen, + } + + atePort2Vlan10 = attrs.Attributes{ + Name: "atePort2Vlan10", + MAC: "00:12:01:00:00:01", + IPv4: "192.0.2.10", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:a", + IPv6Len: ipv6PrefixLen, + } + + dutPort2Vlan20 = attrs.Attributes{ + Desc: "dutPort2Vlan20", + MAC: "01:00:01:01:01:01", + IPv4: "192.0.2.13", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:d", + IPv6Len: ipv6PrefixLen, + } + + atePort2Vlan20 = attrs.Attributes{ + Name: "atePort2Vlan20", + MAC: "00:12:01:00:00:01", + IPv4: "192.0.2.14", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:e", + IPv6Len: ipv6PrefixLen, + } + + dutPort2Vlan30 = attrs.Attributes{ + Desc: "dutPort2Vlan30", + MAC: "01:00:01:01:01:01", + IPv4: "192.0.2.17", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:11", + IPv6Len: ipv6PrefixLen, + } + + atePort2Vlan30 = attrs.Attributes{ + Name: "atePort2Vlan30", + MAC: "00:12:01:00:00:01", + IPv4: "192.0.2.18", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:12", + IPv6Len: ipv6PrefixLen, + } + + dutPort3 = attrs.Attributes{ + Desc: "dutPort3", + MAC: "03:00:01:01:01:01", + IPv4: "192.0.2.21", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:21", + IPv6Len: ipv6PrefixLen, + } + + atePort3 = attrs.Attributes{ + Name: "atePort3", + MAC: "00:12:01:00:00:03", + IPv4: "192.0.2.22", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:22", + IPv6Len: ipv6PrefixLen, + } + + dutPort4 = attrs.Attributes{ + Desc: "dutPort4", + MAC: "04:00:01:01:01:01", + IPv4: "192.0.2.33", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:33", + IPv6Len: ipv6PrefixLen, + } + + atePort4 = attrs.Attributes{ + Name: "atePort4", + MAC: "00:12:01:00:00:04", + IPv4: "192.0.2.34", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:34", + IPv6Len: ipv6PrefixLen, + } + + dutPort4Vlan40 = attrs.Attributes{ + Desc: "dutPort4Vlan40", + MAC: "04:00:01:01:01:01", + IPv4: "192.0.2.37", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:37", + IPv6Len: ipv6PrefixLen, + } + + atePort4Vlan40 = attrs.Attributes{ + Name: "atePort4Vlan40", + MAC: "00:12:01:00:00:04", + IPv4: "192.0.2.38", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:38", + IPv6Len: ipv6PrefixLen, + } + + dutPort4Vlan50 = attrs.Attributes{ + Desc: "dutPort4Vlan50", + MAC: "04:00:01:01:01:01", + IPv4: "192.0.2.41", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:41", + IPv6Len: ipv6PrefixLen, + } + + atePort4Vlan50 = attrs.Attributes{ + Name: "atePort4Vlan50", + MAC: "00:12:01:00:00:04", + IPv4: "192.0.2.42", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:42", + IPv6Len: ipv6PrefixLen, + } + + dutPort4Vlan60 = attrs.Attributes{ + Desc: "dutPort4Vlan60", + MAC: "04:00:01:01:01:01", + IPv4: "192.0.2.45", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:45", + IPv6Len: ipv6PrefixLen, + } + + atePort4Vlan60 = attrs.Attributes{ + Name: "atePort4Vlan60", + MAC: "00:12:01:00:00:04", + IPv4: "192.0.2.46", + IPv4Len: ipv4PrefixLen, + IPv6: "2001:0db8::192:0:2:46", + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// configureATE configures port1, port2, port3, port4 and vlans on port2 and port4 on the ATE. +func configureATE(t *testing.T, ate *ondatra.ATEDevice, dut *ondatra.DUTDevice) gosnappi.Config { + top := gosnappi.NewConfig() + + // ATE Port 1 (Ingress) + p1 := ate.Port(t, "port1") + top.Ports().Add().SetName(p1.ID()) + srcDev1 := top.Devices().Add().SetName(atePort1.Name) + ethSrc1 := srcDev1.Ethernets().Add().SetName(atePort1.Name + ".eth").SetMac(atePort1.MAC) + ethSrc1.Connection().SetPortName(p1.ID()) + ethSrc1.Ipv4Addresses().Add().SetName(srcDev1.Name() + ".ipv4").SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + ethSrc1.Ipv6Addresses().Add().SetName(srcDev1.Name() + ".ipv6").SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + // ATE Port 2 (Egress for Port 1) + p2 := ate.Port(t, "port2") + top.Ports().Add().SetName(p2.ID()) + dstDev2 := top.Devices().Add().SetName(atePort2.Name) + ethDst2 := dstDev2.Ethernets().Add().SetName(atePort2.Name + ".eth").SetMac(atePort2.MAC) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + ethDst2.Vlans().Add().SetName(atePort2.Name + "vlan").SetId(1) + } + ethDst2.Connection().SetPortName(p2.ID()) + ethDst2.Ipv4Addresses().Add().SetName(dstDev2.Name() + ".ipv4").SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + ethDst2.Ipv6Addresses().Add().SetName(dstDev2.Name() + ".ipv6").SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + // Configure VLANs on ATE port2 + dstDev2Vlan10 := top.Devices().Add().SetName(atePort2Vlan10.Name) + ethDst2Vlan10 := dstDev2Vlan10.Ethernets().Add().SetName(atePort2Vlan10.Name + ".eth").SetMac(atePort2Vlan10.MAC) + ethDst2Vlan10.Connection().SetPortName(p2.ID()) + ethDst2Vlan10.Vlans().Add().SetName(atePort2Vlan10.Name + "vlan").SetId(10) + ethDst2Vlan10.Ipv4Addresses().Add().SetName(atePort2Vlan10.Name + ".ipv4").SetAddress(atePort2Vlan10.IPv4).SetGateway(dutPort2Vlan10.IPv4).SetPrefix(uint32(atePort2Vlan10.IPv4Len)) + ethDst2Vlan10.Ipv6Addresses().Add().SetName(atePort2Vlan10.Name + ".ipv6").SetAddress(atePort2Vlan10.IPv6).SetGateway(dutPort2Vlan10.IPv6).SetPrefix(uint32(atePort2Vlan10.IPv6Len)) + + dstDev2Vlan20 := top.Devices().Add().SetName(atePort2Vlan20.Name) + ethDst2Vlan20 := dstDev2Vlan20.Ethernets().Add().SetName(atePort2Vlan20.Name + ".eth").SetMac(atePort2Vlan20.MAC) + ethDst2Vlan20.Connection().SetPortName(p2.ID()) + ethDst2Vlan20.Vlans().Add().SetName(atePort2Vlan20.Name + "vlan").SetId(20) + ethDst2Vlan20.Ipv4Addresses().Add().SetName(atePort2Vlan20.Name + ".ipv4").SetAddress(atePort2Vlan20.IPv4).SetGateway(dutPort2Vlan20.IPv4).SetPrefix(uint32(atePort2Vlan20.IPv4Len)) + ethDst2Vlan20.Ipv6Addresses().Add().SetName(atePort2Vlan20.Name + ".ipv6").SetAddress(atePort2Vlan20.IPv6).SetGateway(dutPort2Vlan20.IPv6).SetPrefix(uint32(atePort2Vlan20.IPv6Len)) + + dstDev2Vlan30 := top.Devices().Add().SetName(atePort2Vlan30.Name) + ethDst2Vlan30 := dstDev2Vlan30.Ethernets().Add().SetName(atePort2Vlan30.Name + ".eth").SetMac(atePort2Vlan30.MAC) + ethDst2Vlan30.Connection().SetPortName(p2.ID()) + ethDst2Vlan30.Vlans().Add().SetName(atePort2Vlan30.Name + "vlan").SetId(30) + ethDst2Vlan30.Ipv4Addresses().Add().SetName(atePort2Vlan30.Name + ".ipv4").SetAddress(atePort2Vlan30.IPv4).SetGateway(dutPort2Vlan30.IPv4).SetPrefix(uint32(atePort2Vlan30.IPv4Len)) + ethDst2Vlan30.Ipv6Addresses().Add().SetName(atePort2Vlan30.Name + ".ipv6").SetAddress(atePort2Vlan30.IPv6).SetGateway(dutPort2Vlan30.IPv6).SetPrefix(uint32(atePort2Vlan30.IPv6Len)) + + // ATE Port 3 (Ingress) + p3 := ate.Port(t, "port3") + top.Ports().Add().SetName(p3.ID()) + srcDev3 := top.Devices().Add().SetName(atePort3.Name) + ethSrc3 := srcDev3.Ethernets().Add().SetName(atePort3.Name + ".eth").SetMac(atePort3.MAC) + ethSrc3.Connection().SetPortName(p3.ID()) + ethSrc3.Ipv4Addresses().Add().SetName(srcDev3.Name() + ".ipv4").SetAddress(atePort3.IPv4).SetGateway(dutPort3.IPv4).SetPrefix(uint32(atePort3.IPv4Len)) + ethSrc3.Ipv6Addresses().Add().SetName(srcDev3.Name() + ".ipv6").SetAddress(atePort3.IPv6).SetGateway(dutPort3.IPv6).SetPrefix(uint32(atePort3.IPv6Len)) + + // ATE Port 4 (Egress for Port 3) + p4 := ate.Port(t, "port4") + top.Ports().Add().SetName(p4.ID()) + dstDev4 := top.Devices().Add().SetName(atePort4.Name) + ethDst4 := dstDev4.Ethernets().Add().SetName(atePort4.Name + ".eth").SetMac(atePort4.MAC) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + ethDst4.Vlans().Add().SetName(atePort4.Name + "vlan").SetId(1) + } + ethDst4.Connection().SetPortName(p4.ID()) + ethDst4.Ipv4Addresses().Add().SetName(dstDev4.Name() + ".ipv4").SetAddress(atePort4.IPv4).SetGateway(dutPort4.IPv4).SetPrefix(uint32(atePort4.IPv4Len)) + ethDst4.Ipv6Addresses().Add().SetName(dstDev4.Name() + ".ipv6").SetAddress(atePort4.IPv6).SetGateway(dutPort4.IPv6).SetPrefix(uint32(atePort4.IPv6Len)) + + // Configure VLANs on ATE port4 + dstDev4Vlan40 := top.Devices().Add().SetName(atePort4Vlan40.Name) + ethDst4Vlan40 := dstDev4Vlan40.Ethernets().Add().SetName(atePort4Vlan40.Name + ".eth").SetMac(atePort4Vlan40.MAC) + ethDst4Vlan40.Connection().SetPortName(p4.ID()) + ethDst4Vlan40.Vlans().Add().SetName(atePort4Vlan40.Name + "vlan").SetId(40) + ethDst4Vlan40.Ipv4Addresses().Add().SetName(atePort4Vlan40.Name + ".ipv4").SetAddress(atePort4Vlan40.IPv4).SetGateway(dutPort4Vlan40.IPv4).SetPrefix(uint32(atePort4Vlan40.IPv4Len)) + ethDst4Vlan40.Ipv6Addresses().Add().SetName(atePort4Vlan40.Name + ".ipv6").SetAddress(atePort4Vlan40.IPv6).SetGateway(dutPort4Vlan40.IPv6).SetPrefix(uint32(atePort4Vlan40.IPv6Len)) + + dstDev4Vlan50 := top.Devices().Add().SetName(atePort4Vlan50.Name) + ethDst4Vlan50 := dstDev4Vlan50.Ethernets().Add().SetName(atePort4Vlan50.Name + ".eth").SetMac(atePort4Vlan50.MAC) + ethDst4Vlan50.Connection().SetPortName(p4.ID()) + ethDst4Vlan50.Vlans().Add().SetName(atePort4Vlan50.Name + "vlan").SetId(50) + ethDst4Vlan50.Ipv4Addresses().Add().SetName(atePort4Vlan50.Name + ".ipv4").SetAddress(atePort4Vlan50.IPv4).SetGateway(dutPort4Vlan50.IPv4).SetPrefix(uint32(atePort4Vlan50.IPv4Len)) + ethDst4Vlan50.Ipv6Addresses().Add().SetName(atePort4Vlan50.Name + ".ipv6").SetAddress(atePort4Vlan50.IPv6).SetGateway(dutPort4Vlan50.IPv6).SetPrefix(uint32(atePort4Vlan50.IPv6Len)) + + dstDev4Vlan60 := top.Devices().Add().SetName(atePort4Vlan60.Name) + ethDst4Vlan60 := dstDev4Vlan60.Ethernets().Add().SetName(atePort4Vlan60.Name + ".eth").SetMac(atePort4Vlan60.MAC) + ethDst4Vlan60.Connection().SetPortName(p4.ID()) + ethDst4Vlan60.Vlans().Add().SetName(atePort4Vlan60.Name + "vlan").SetId(60) + ethDst4Vlan60.Ipv4Addresses().Add().SetName(atePort4Vlan60.Name + ".ipv4").SetAddress(atePort4Vlan60.IPv4).SetGateway(dutPort4Vlan60.IPv4).SetPrefix(uint32(atePort4Vlan60.IPv4Len)) + ethDst4Vlan60.Ipv6Addresses().Add().SetName(atePort4Vlan60.Name + ".ipv6").SetAddress(atePort4Vlan60.IPv6).SetGateway(dutPort4Vlan60.IPv6).SetPrefix(uint32(atePort4Vlan60.IPv6Len)) + + return top +} + +// configNetworkInstance creates VRFs and subinterfaces and then applies VRFs on the subinterfaces. +func configNetworkInstance(t *testing.T, dut *ondatra.DUTDevice, vrfname string, intfname string, subint uint32, vlanID uint16) { + // create empty subinterface + si := &oc.Interface_Subinterface{} + si.Index = ygot.Uint32(subint) + if deviations.DeprecatedVlanID(dut) { + si.GetOrCreateVlan().VlanId = oc.UnionUint16(vlanID) + } else { + si.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(vlanID) + } + s4 := si.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + gnmi.Replace(t, dut, gnmi.OC().Interface(intfname).Subinterface(subint).Config(), si) + + // create vrf and apply on subinterface + v := &oc.NetworkInstance{ + Name: ygot.String(vrfname), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF, + } + vi := v.GetOrCreateInterface(intfname + "." + strconv.Itoa(int(subint))) + vi.Interface = ygot.String(intfname) + vi.Subinterface = ygot.Uint32(subint) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(vrfname).Config(), v) +} + +// getSubInterface returns a subinterface configuration populated with IP addresses and VLAN ID. +func getSubInterface(dutPort *attrs.Attributes, index uint32, vlanID uint16, dut *ondatra.DUTDevice) *oc.Interface_Subinterface { + s := &oc.Interface_Subinterface{} + // unshut sub/interface + if deviations.InterfaceEnabled(dut) { + s.Enabled = ygot.Bool(true) + } + s.Index = ygot.Uint32(index) + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + a := s4.GetOrCreateAddress(dutPort.IPv4) + a.PrefixLength = ygot.Uint8(dutPort.IPv4Len) + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) { + s6.Enabled = ygot.Bool(true) + } + a6 := s6.GetOrCreateAddress(dutPort.IPv6) + a6.PrefixLength = ygot.Uint8(dutPort.IPv6Len) + if index != 0 { + if deviations.DeprecatedVlanID(dut) { + s.GetOrCreateVlan().VlanId = oc.UnionUint16(vlanID) + } else { + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(vlanID) + } + } + return s +} + +// configInterfaceDUT configures the interface with the Addrs. +func configInterfaceDUT(i *oc.Interface, dutPort *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + i.Description = ygot.String(dutPort.Desc) + i.Type = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + i.AppendSubinterface(getSubInterface(dutPort, 0, 0, dut)) + return i +} + +// configureDUT configures the base configuration on the DUT. +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + + // DUT Port 1 (Ingress) + p1 := dut.Port(t, "port1") + i1 := &oc.Interface{Name: ygot.String(p1.Name())} + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(i1, &dutPort1, dut)) + + // DUT Port 2 (Egress for Port 1) + p2 := dut.Port(t, "port2") + i2 := &oc.Interface{Name: ygot.String(p2.Name())} + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(i2, &dutPort2, dut)) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + i3 := &oc.Interface{Name: ygot.String(p2.Name())} + s := i3.GetOrCreateSubinterface(0) + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(1) + gnmi.Update(t, dut, d.Interface(p2.Name()).Config(), i3) + } + + outpath2 := d.Interface(p2.Name()) + // create VRFs and VRF enabled subinterfaces for port2 + configNetworkInstance(t, dut, "VRF10", p2.Name(), uint32(1), 10) + gnmi.Update(t, dut, outpath2.Subinterface(1).Config(), getSubInterface(&dutPort2Vlan10, 1, 10, dut)) + + configNetworkInstance(t, dut, "VRF20", p2.Name(), uint32(2), 20) + gnmi.Update(t, dut, outpath2.Subinterface(2).Config(), getSubInterface(&dutPort2Vlan20, 2, 20, dut)) + + configNetworkInstance(t, dut, "VRF30", p2.Name(), uint32(3), 30) + gnmi.Update(t, dut, outpath2.Subinterface(3).Config(), getSubInterface(&dutPort2Vlan30, 3, 30, dut)) + + // DUT Port 3 (Ingress) + p3 := dut.Port(t, "port3") + i4 := &oc.Interface{Name: ygot.String(p3.Name())} + gnmi.Replace(t, dut, d.Interface(p3.Name()).Config(), configInterfaceDUT(i4, &dutPort3, dut)) + + // DUT Port 4 (Egress for Port 3) + p4 := dut.Port(t, "port4") + i5 := &oc.Interface{Name: ygot.String(p4.Name())} + gnmi.Replace(t, dut, d.Interface(p4.Name()).Config(), configInterfaceDUT(i5, &dutPort4, dut)) + if deviations.NoMixOfTaggedAndUntaggedSubinterfaces(dut) { + i6 := &oc.Interface{Name: ygot.String(p4.Name())} + s := i6.GetOrCreateSubinterface(0) + s.GetOrCreateVlan().GetOrCreateMatch().GetOrCreateSingleTagged().VlanId = ygot.Uint16(1) + gnmi.Update(t, dut, d.Interface(p4.Name()).Config(), i6) + } + + outpath4 := d.Interface(p4.Name()) + // create VRFs and VRF enabled subinterfaces for port4 + configNetworkInstance(t, dut, "VRF40", p4.Name(), uint32(4), 40) + gnmi.Update(t, dut, outpath4.Subinterface(4).Config(), getSubInterface(&dutPort4Vlan40, 4, 40, dut)) + + configNetworkInstance(t, dut, "VRF50", p4.Name(), uint32(5), 50) + gnmi.Update(t, dut, outpath4.Subinterface(5).Config(), getSubInterface(&dutPort4Vlan50, 5, 50, dut)) + + configNetworkInstance(t, dut, "VRF60", p4.Name(), uint32(6), 60) + gnmi.Update(t, dut, outpath4.Subinterface(6).Config(), getSubInterface(&dutPort4Vlan60, 6, 60, dut)) + + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, p1) + fptest.SetPortSpeed(t, p2) + fptest.SetPortSpeed(t, p3) + fptest.SetPortSpeed(t, p4) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p3.Name(), deviations.DefaultNetworkInstance(dut), 0) + fptest.AssignToNetworkInstance(t, dut, p4.Name(), deviations.DefaultNetworkInstance(dut), 0) // Assign port4 to default VRF + } +} + +// getIPinIPFlow returns an IPv4inIPv4 *ondatra.Flow with provided DSCP value for a given set of endpoints. +func getIPinIPFlow(args *testArgs, src attrs.Attributes, dst attrs.Attributes, flowName string, dscp uint32) gosnappi.Flow { + + flow := gosnappi.NewFlow().SetName(flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{src.Name + "." + args.iptype}).SetRxNames([]string{dst.Name + "." + args.iptype}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(src.MAC) + outerIPHeader := flow.Packet().Add().Ipv4() + outerIPHeader.Src().SetValue(src.IPv4) + outerIPHeader.Dst().SetValue(dst.IPv4) + outerIPHeader.Priority().Dscp().Phb().SetValue(dscp) + innerIPHeader := flow.Packet().Add().Ipv4() + innerIPHeader.Src().SetValue("198.51.100.1") + innerIPHeader.Dst().Increment().SetStart("203.0.113.1").SetStep("0.0.0.1").SetCount(10000) + + flow.Size().SetFixed(1024) + flow.Rate().SetPps(100) + flow.Duration().FixedPackets().SetPackets(100) + + return flow +} + +// testTrafficFlows verifies traffic for one or more flows. +func testTrafficFlows(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, expectPass bool, flows ...gosnappi.Flow) { + + top.Flows().Clear() + for _, flow := range flows { + top.Flows().Append(flow) + } + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + t.Logf("*** Starting traffic ...") + ate.OTG().StartTraffic(t) + time.Sleep(trafficDuration) + t.Logf("*** Stop traffic ...") + ate.OTG().StopTraffic(t) + + if expectPass { + t.Log("Expecting traffic to pass for the flows") + } else { + t.Log("Expecting traffic to fail for the flows") + } + + otgTop := ate.OTG().GetConfig(t) + otgutils.LogFlowMetrics(t, ate.OTG(), otgTop) + for _, flow := range flows { + t.Run(flow.Name(), func(t *testing.T) { + t.Logf("*** Verifying %v traffic on OTG ... ", flow.Name()) + outPkts := float32(gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State())) + inPkts := float32(gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State())) + + if outPkts == 0 { + t.Fatalf("OutPkts == 0, want >0.") + } + + lossPct := (outPkts - inPkts) * 100 / outPkts + + // log stats + t.Log("Flow LossPct: ", lossPct) + t.Log("Flow InPkts : ", inPkts) + t.Log("Flow OutPkts : ", outPkts) + + if (expectPass == true) && (lossPct == 0) { + t.Logf("Traffic for %v flow is passing as expected", flow.Name()) + } else if (expectPass == false) && (lossPct == 100) { + t.Logf("Traffic for %v flow is failing as expected", flow.Name()) + } else { + t.Fatalf("Traffic is not working as expected for flow: %v. LossPct: %f", flow.Name(), lossPct) + } + }) + } +} + +// getL3PBRRule returns an IPv4 or IPv6 policy-forwarding rule configuration populated with protocol and/or DSCPset information. +func getL3PBRRule(args *testArgs, networkInstance string, index uint32, dscpset []uint8) *oc.NetworkInstance_PolicyForwarding_Policy_Rule { + r := oc.NetworkInstance_PolicyForwarding_Policy_Rule{} + r.SequenceId = ygot.Uint32(index) + r.Action = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action{NetworkInstance: ygot.String(networkInstance)} + if args.iptype == "ipv4" { + r.Ipv4 = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Ipv4{ + Protocol: args.protocol, + } + if len(dscpset) > 0 { + r.Ipv4.DscpSet = dscpset + } + } else if args.iptype == "ipv6" { + r.Ipv6 = &oc.NetworkInstance_PolicyForwarding_Policy_Rule_Ipv6{ + Protocol: args.protocol, + } + if len(dscpset) > 0 { + r.Ipv6.DscpSet = dscpset + } + } else { + return nil + } + return &r + +} + +// getPBRPolicyForwarding returns pointer to policy-forwarding populated with pbr policy and rules +func getPBRPolicyForwarding(policyName string, rules ...*oc.NetworkInstance_PolicyForwarding_Policy_Rule) *oc.NetworkInstance_PolicyForwarding { + pf := oc.NetworkInstance_PolicyForwarding{} + p := pf.GetOrCreatePolicy(policyName) + p.Type = oc.Policy_Type_VRF_SELECTION_POLICY + for _, rule := range rules { + p.AppendRule(rule) + } + return &pf +} + +func TestPBR(t *testing.T) { + t.Logf("Description: Test RT3.2 with multiple DSCP, IPinIP protocol rule based VRF selection with two ingress/egress pairs.") + dut := ondatra.DUT(t, "dut") + + // configure DUT + configureDUT(t, dut) + + // Configure ATE + ate := ondatra.ATE(t, "ate") + top := configureATE(t, ate, dut) + ate.OTG().PushConfig(t, top) + ate.OTG().StartProtocols(t) + + // Ingress interface for policies + port1 := dut.Port(t, "port1") + port3 := dut.Port(t, "port3") + + cases := []struct { + name string + desc string + policy *oc.NetworkInstance_PolicyForwarding + policyName string + ingressPort *ondatra.Port + testArgs *testArgs + passingFlows []gosnappi.Flow + failingFlows []gosnappi.Flow + rejectable bool + }{ + { + name: "RT3.2 Case1 - Port 1 Ingress to Port 2 Egress", + desc: "Ensure matching IPinIP with DSCP (10 - VRF10, 20- VRF20, 30-VRF30) traffic reaches appropriate VLAN on Port 2 (Egress for Port 1).", + policyName: "L3_Port1", + ingressPort: port1, + testArgs: &testArgs{ + dut: dut, + ate: ate, + top: top, + iptype: "ipv4", + protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, + }, + policy: getPBRPolicyForwarding("L3_Port1", + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF10", 1, []uint8{10}), + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF20", 2, []uint8{20}), + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF30", 3, []uint8{30})), + passingFlows: []gosnappi.Flow{ + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd10_p1_p2", 10), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd20_p1_p2", 20), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan30, "ipinipd30_p1_p2", 30)}, + }, + { + name: "RT3.2 Case2 - Port 1 Ingress to Port 2 Egress (Range DSCP)", + desc: "Ensure matching IPinIP with DSCP (10-12 - VRF10, 20-22- VRF20, 30-32-VRF30) traffic reaches appropriate VLAN on Port 2 (Egress for Port 1).", + policyName: "L3_Port1", + ingressPort: port1, + testArgs: &testArgs{ + dut: dut, + ate: ate, + top: top, + iptype: "ipv4", + protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, + }, + policy: getPBRPolicyForwarding("L3_Port1", + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF10", 1, []uint8{10, 11, 12}), + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF20", 2, []uint8{20, 21, 22}), + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF30", 3, []uint8{30, 31, 32})), + passingFlows: []gosnappi.Flow{ + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd10_p1_p2", 10), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd11_p1_p2", 11), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd12_p1_p2", 12), + + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd20_p1_p2", 20), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd21_p1_p2", 21), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd22_p1_p2", 22), + + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan30, "ipinipd30_p1_p2", 30), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan30, "ipinipd31_p1_p2", 31), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan30, "ipinipd32_p1_p2", 32)}, + }, + { + name: "RT3.2 Case3 - Port 1 Ingress (Precedence)", + desc: "Ensure first matching of IPinIP with DSCP (10-12 - VRF10, 10-12 - VRF20) rule takes precedence on Port 1.", + policyName: "L3_Port1", + ingressPort: port1, + testArgs: &testArgs{ + dut: dut, + ate: ate, + top: top, + iptype: "ipv4", + protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, + }, + policy: getPBRPolicyForwarding("L3_Port1", + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF10", 1, []uint8{10, 11, 12}), + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF20", 2, []uint8{10, 11, 12})), + passingFlows: []gosnappi.Flow{ + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd10_p1_p2", 10), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd11_p1_p2", 11), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd12_p1_p2", 12)}, + failingFlows: []gosnappi.Flow{ + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd10v20_p1_p2", 10), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd11v20_p1_p2", 11), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd12v20_p1_p2", 12)}, + rejectable: true, + }, + { + name: "RT3.2 Case4 - Port 1 Ingress (Default Match)", + desc: "Ensure matching IPinIP to VRF10, IPinIP with DSCP20 to VRF20 causes unspecified DSCP IPinIP traffic to match VRF10 on Port 1.", + policyName: "L3_Port1", + ingressPort: port1, + testArgs: &testArgs{ + dut: dut, + ate: ate, + top: top, + iptype: "ipv4", + protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, + }, + policy: getPBRPolicyForwarding("L3_Port1", + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF10", 1, []uint8{}), + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF20", 2, []uint8{20})), + passingFlows: []gosnappi.Flow{ + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd10_p1_p2", 10), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd11_p1_p2", 11), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd12_p1_p2", 12)}, + failingFlows: []gosnappi.Flow{ + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd10v20_p1_p2", 10), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd11v20_p1_p2", 11), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd12v20_p1_p2", 12), + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd20_p1_p2", 20)}, + }, + { + name: "RT3.2 Case5 - Multiple Policies (Port 1 and Port 3)", + desc: "Verify traffic for both policies simultaneously: Port 1 to Port 2 (VLAN 10) and Port 3 to Port 4 (VLAN 40).", + policyName: "L3_Port1", // Second policy (Port 3) will be applied within the test run + ingressPort: port1, + testArgs: &testArgs{ // Args for Port 1's flow + dut: dut, + ate: ate, + top: top, + iptype: "ipv4", + protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, + }, + policy: getPBRPolicyForwarding("L3_Port1", + getL3PBRRule(&testArgs{iptype: "ipv4", protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP}, "VRF10", 1, []uint8{10})), + passingFlows: []gosnappi.Flow{ + // Initial flow for Port 1's policy + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan10, "ipinipd10-multi_p1_p2", 10), + }, + failingFlows: []gosnappi.Flow{ + // Initial flow for Port 1's policy + getIPinIPFlow(&testArgs{iptype: "ipv4"}, atePort1, atePort2Vlan20, "ipinipd21_p1_p2", 21), + }, + }, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + t.Log(tc.desc) + pfpath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding() + // Configure pbr policy-forwarding + fptest.ConfigureDefaultNetworkInstance(t, dut) + errMsg := testt.CaptureFatal(t, func(t testing.TB) { + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), tc.policy) + }) + if errMsg != nil { + if tc.rejectable { + t.Skipf("Skipping test case %q, PolicyForwarding config was rejected with an error: %s", tc.name, *errMsg) + } + t.Fatalf("PolicyForwarding config update failed: %v", *errMsg) + } + // Defer cleaning policy-forwarding + defer gnmi.Delete(t, dut, pfpath.Config()) + + // Apply PBR policy on the ingress interface + ingressPortName := tc.ingressPort.Name() + d := &oc.Root{} + interfaceID := ingressPortName + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interfaceID = ingressPortName + ".0" + } + pfIntf := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreatePolicyForwarding().GetOrCreateInterface(interfaceID) + pfIntfConfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Interface(interfaceID) + pfIntf.GetOrCreateInterfaceRef().Interface = ygot.String(ingressPortName) + pfIntf.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + pfIntf.InterfaceRef = nil + } + pfIntf.SetApplyVrfSelectionPolicy(tc.policyName) + gnmi.Update(t, dut, pfIntfConfPath.Config(), pfIntf) + + // Defer deletion of policy from interface + defer gnmi.Delete(t, dut, pfIntfConfPath.Config()) + + // "Multiple Policies" test case where a second policy is applied to Port 3 + if tc.name == "RT3.2 Case5 - Multiple Policies (Port 1 and Port 3)" { + t.Log("Applying second policy (L3_Port3) to Port 3 for multiple policy scenario.") + // Define testArgs for the second policy's flows and rules + argsForPort3Policy := &testArgs{ + dut: dut, + ate: ate, + top: top, + iptype: "ipv4", + protocol: oc.PacketMatchTypes_IP_PROTOCOL_IP_IN_IP, + } + multiPolicy := getPBRPolicyForwarding("L3_Port3", getL3PBRRule(argsForPort3Policy, "VRF40", 1, []uint8{40})) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), multiPolicy) + defer gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy("L3_Port3").Config()) + interface3ID := port3.Name() + if deviations.InterfaceRefInterfaceIDFormat(dut) { + interface3ID = port3.Name() + ".0" + } + pfIntfPort3 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreatePolicyForwarding().GetOrCreateInterface(interface3ID) + pfIntfConfPathPort3 := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Interface(interface3ID) + pfIntfPort3.GetOrCreateInterfaceRef().Interface = ygot.String(port3.Name()) + pfIntfPort3.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) + if deviations.InterfaceRefConfigUnsupported(dut) { + pfIntfPort3.InterfaceRef = nil + } + pfIntfPort3.SetApplyVrfSelectionPolicy("L3_Port3") + gnmi.Update(t, dut, pfIntfConfPathPort3.Config(), pfIntfPort3) + defer gnmi.Delete(t, dut, pfIntfConfPathPort3.Config()) + + // Add flows for Port 3 to the passing flows + tc.passingFlows = append(tc.passingFlows, getIPinIPFlow(argsForPort3Policy, atePort3, atePort4Vlan40, "ipinipd40-multi_p3_p4", 40)) + tc.failingFlows = append(tc.failingFlows, getIPinIPFlow(argsForPort3Policy, atePort3, atePort4Vlan50, "ipinipd50-multi_p3_p4", 51)) + } + + // traffic should pass + testTrafficFlows(t, ate, top, true, tc.passingFlows...) + + if len(tc.failingFlows) > 0 { + // traffic should fail + testTrafficFlows(t, ate, top, false, tc.failingFlows...) + } + }) + } +} diff --git a/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/README.md b/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/README.md new file mode 100644 index 00000000000..41e9273b922 --- /dev/null +++ b/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/README.md @@ -0,0 +1,292 @@ +# PF-1.6: Policy based VRF selection for IPV4/IPV6 + +## Summary +This test ensures NOS is able to host multiple VRFs, and verify that VRF selection policy is working as intended. + + +## Test environment setup + +### Topology +Create the following connections: +```mermaid +graph LR; +subgraph ATE1 [ATE1] + A1[Port1] +end +subgraph DUT1 [DUT1] + B3[Port1-default VRF] + B1[Port2-default VRF] + B2[Port3-nondefault VRF] +end +subgraph ATE2 [ATE2] + C1[Port1] + C2[Port2] +end +A1 <-- IBGP(ASN100) --> B3; +B1 <-- EBGP(ASN100:ASN200) --> C1; +B2 <-- EBGP(ASN100:ASN200) --> C2; +B3 --> B1; +``` +* Traffic Flow direction is from ATE1 --> ATE2 + +### Configuration generation of DUT and ATE + +#### DUT Configuration +* Configure IBGP[ASN100] as described in topology between ATE1:Port1 and DUT1:Port1 +* Configure EBGP[ASN200] between DUT1:Port2 and ATE2:Port1 +* Configure EBGP[ASN200] between DUT1:Port3 and ATE2:Port2 +* Port1 of DUT1 is connected to Port1 of ATE1 +* Port2 of DUT1 which maps to Default VRF instance, is connected to Port1 of ATE2 +* Port3 of DUT1 which maps to the non-default VRF instance, is connected to Port2 of ATE2 +* Configure a policy based traffic steering from default to Non Default VRF, this policy should be able to steer the traffic from Default VRF to non default VRF and vice versa based on the destination IPv4/IPv6 address. +* DUT has the following VRF selection policy initially + * Statement1: traffic matching IPv4Prefix1/24, forwards the traffic through default vrf + * Statement2: traffic matching IPv4Prefix2/24, forwards the traffic through default vrf + * Statement3: traffic matching IPv6Prefix3/64, forwards the traffic through default vrf + * Statement4: traffic matching IPv6Prefix4/64, forwards the traffic through default vrf + * Setup the Default VRF and the non-default VRF such that they share the same routes + +#### ATE Configuration +* Configure IBGP[ASN100] on ATE1:Port1 +* Configure EBGP[ASN200] on ATE2:Port1 & ATE2:Port2 + +### Configure ATE Route Advertisements & Traffic Flows as below: +#### ATE Route Advertisements: + + ATE2:Port1 advertises following prefixes to DUT1:Port2 over EBGP + - IPv4Prefix1/24 + - IPv4Prefix2/24 + - IPv6Prefix3/64 + - IPv6Prefix4/64 + + ATE2:Port2 advertieses following prefixes to DUT1:Port3 over EBGP + - IPv4Prefix1/24 + - IPv4Prefix2/24 + - IPv6Prefix3/64 + - IPv6Prefix4/64 + +#### ATE traffic Flows: + + From ATE1:Port1 to ATE2 destination prefixes + - IPv4Prefix1/24 at a rate of 100 packets/sec + - IPv4Prefix2/24 at a rate of 100 packets/sec + - IPv6Prefix3/64 at a rate of 100 packets/sec + - IPv6Prefix4/64 at a rate of 100 packets/sec + + +## Procedure +### PF-1.6.1: [Baseline] Default VRF for all flows with regular traffic profile + +#### In this case DUT1:Port1 sends the regular traffic flows to ATE2:Port1. + * ATE2:Port1 receives following IPv4 and IPv6 flows: + * IPv4Prefix1/24 + * IPv4Prefix2/24 + * IPv6Prefix3/64 + * IPv6Prefix4/64 + + * Validation + * All traffic must be successful and there should be 0 packet loss. + * Need to verify the packets sent by sender tester is equal to the packets on receiving tester port + * DUT1:Port2 port out-pkts counter should match packets sent by ATE1:PORT1 + * DUT1:Port2 port out-pkts counter should match packets sent by ATE1:PORT1. + +### PF-1.6.2: Traffic from ATE1 to ATE2, 1 Prefix migrated to Non-Default VRF using the VRF selection policy + * ATE1:Port1 sends following IPv4 and IPv6 flows: + * IPv4Prefix1/24 + * IPv4Prefix2/24 + * IPv6Prefix3/64 + * IPv6Prefix4/64 + + * VRF selection policy on DUT1:Port2 changes as follows: + * Statement1: traffic matching IPv4Prefix1/24, Punt to non-default vrf by the policy + * Statement2: traffic matching IPv4Prefix2/24, is forwarded through the default vrf + * Statement3: traffic matching IPv6Prefix3/64, is forwarded through the default vrf + * Statement4: traffic matching IPv6Prefix4/64, is forwarded through the default vrf + + * Validation + * Validate the prefixes advertised by ATE2:Port1 and ATE2:Port2 are received on ATE1:Port1 + * Traffic for Prefix 1 received from ATE1:Port1 once punted to non-defailt VRF by the VRF selection policy, must be received by ATE2:Port2 + * Traffic for rest of the prefixes sent by ATE1:Port1 must be routed to ATE2:Port1 via the DEFAULT VRF in the DUT. + * Need to verify the packets sent by sender tester is equal to the packets on receiving tester ports + * The flow packets sent for IPv4Prefix1/24 by ATE1:Port3 should be equal packets to DUT1:Port3 out-pkts counter. + * The sum of packets sent for flow prefixes IPv4Prefix2/240, IPv6Prefix3/24 and IPv6Prefix4/24 should be equal packets to DUT1:Port2 out-pkts counter. + * There should be 0 packet loss. + +### PF-1.6.3: Traffic from ATE1 to ATE2, 2 Prefixes migrated to Non-Default VRF using the VRF selection policy + * ATE1:Port1 sends following IPv4 and IPv6 flows: + * IPv4Prefix1/24 + * IPv4Prefix2/24 + * IPv6Prefix3/64 + * IPv6Prefix4/64 + + * VRF selection policy on DUT1:Port2 changes as follows: + * Statement1: traffic matching IPv4Prefix1/24, Punt to non-default vrf by the policy + * Statement2: traffic matching IPv4Prefix2/24, Punt to non-default vrf by the policy + * Statement3: traffic matching IPv6Prefix3/64, is forwarded through the default vrf + * Statement4: traffic matching IPv6Prefix4/64, is forwarded through the default vrf + + * Validation + * Traffic for IPv4Prefix1/24 & IPv4Prefix2/24 received from ATE1:Port1 once punted to non-defailt VRF by the VRF selection policy, must be received by ATE2:Port2 + * Traffic for Prefix 1 & 2 received from ATE1:Port1 once punted to non-defailt VRF by the VRF selection policy, must be received by ATE2:Port2 + * Traffic for rest of the prefixes sent by ATE1:Port1 must be routed to ATE2:Port1 via the DEFAULT VRF in the DUT. + * Need to verify the packets sent by sender tester is equal to the packets on receiving tester ports. + * The sum of flow packets sent for flow prefixes IPv4Prefix1/24 and IPv4Prefix2/24 by ATE1:Port3 should be equal packets to DUT1:Port3 out-pkts counter. + * The sum of packets sent for flow prefixes IPv6Prefix3/24 and IPv6Prefix4/24 should be equal packets to DUT1:Port2 out-pkts counter. + * There should be 0 packet loss. + +### PF-1.6.4: Traffic from ATE1 to ATE2, 3 Prefixes migrated to Non-Default VRF using the VRF selection policy + * ATE1:Port1 sends following IPv4 and IPv6 flows: + * IPv4Prefix1/24 + * IPv4Prefix2/24 + * IPv6Prefix3/64 + * IPv6Prefix4/64 + + * VRF selection policy on DUT1:Port2 changes as follows: + * Statement1: traffic matching IPv4Prefix1/24, Punt to non-default vrf by the policy + * Statement2: traffic matching IPv4Prefix2/24, Punt to non-default vrf by the policy + * Statement3: traffic matching IPv6Prefix3/64, Punt to non-default vrf by the policy + * Statement4: traffic matching IPv6Prefix4/64, is forwarded through the default vrf + + * Validation + * Validate the prefixes advertised by ATE1:Port1 are received on ATE2:Port1 and ATE2:Port2. + * Traffic for Prefix 1,2 & 3 received from ATE1:Port1 once punted to non-defailt VRF by the VRF selection policy, must be received by ATE2:Port2 + * Traffic for rest of the prefixes sent by ATE1:Port1 must be routed to ATE2:Port1 via the DEFAULT VRF in the DUT. + * Need to verify the packets sent by sender tester is equal to the packets on receiving tester ports. + * The sum of flow packets sent for flow prefixes IPv4Prefix1/24, IPv4Prefix2/24 and IPv6Prefix3/24 by ATE1:Port3 should be equal packets to DUT1:Port3 out-pkts counter. + * The packets sent for flow prefixes IPv6Prefix4/24 should be equal packets to DUT1:Port2 out-pkts counter. + * There should be 0 packet loss. + +### PF-1.6.5: Traffic from ATE1 to ATE2, 4 Prefixes migrated to Non-Default VRF using the VRF selection policy + * ATE1:Port1 sends following IPv4 and IPv6 flows: + * IPv4Prefix1/24 + * IPv4Prefix2/24 + * IPv6Prefix3/64 + * IPv6Prefix4/64 + + * VRF selection policy on DUT1:Port2 changes as follows: + * Statement1: traffic matching IPv4Prefix1/24, Punt to non-default vrf by the policy + * Statement2: traffic matching IPv4Prefix2/24, Punt to non-default vrf by the policy + * Statement3: traffic matching IPv6Prefix3/64, Punt to non-default vrf by the policy + * Statement4: traffic matching IPv6Prefix4/64, Punt to non-default vrf by the policy + + * Validation + * To validate the prefixes advertised by ATE1:Port1 are received on ATE2:Port1 and ATE2:Port2. + * Traffic for all Prefixes received from ATE1:Port1 once punted to non-defailt VRF by the VRF selection policy, must be received by ATE2:Port2 + * No traffic should be routed to ATE2:Port1 via the DEFAULT VRF in the DUT in this case. + * Need to verify the packets sent by sender tester is equal to the packets on receiving tester ports. + * DUT1:Port3 port out-pkts counter should match packets sent by ATE1:PORT1. + * There should be 0 packet loss. + +## Canonical OC + +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "Customer A", + "name": "eth0", + "type": "ethernetCsmacd" + }, + "name": "eth0" + } + ] + }, + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "policy-forwarding": { + "interfaces": { + "interface": [ + { + "config": { + "apply-forwarding-policy": "vrf select", + "interface-id": "eth0" + }, + "interface-id": "eth0" + } + ] + }, + "policies": { + "policy": [ + { + "config": { + "policy-id": "vrf select" + }, + "policy-id": "vrf select", + "rules": { + "rule": [ + { + "action": { + "config": { + "network-instance": "DATA_VRF" + } + }, + "config": { + "sequence-id": 10 + }, + "ipv4": { + "config": { + "destination-address": "10.10.10.1/32", + "source-address": "192.168.1.0/32" + } + }, + "sequence-id": 10 + }, + { + "action": { + "config": { + "discard": true + } + }, + "config": { + "sequence-id": 20 + }, + "ipv6": { + "config": { + "source-address": "2001:db8:1::/64" + } + }, + "sequence-id": 20 + } + ] + } + } + ] + } + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /network-instances/network-instance/name: + /network-instances/network-instance/config/name: + /network-instances/network-instance/config/type: + /network-instances/network-instance/config/description: + /network-instances/network-instance/config/router-id: + /network-instances/network-instance/config/route-distinguisher: + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/interface-id: + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-vrf-selection-policy: + /network-instances/network-instance/policy-forwarding/interfaces/interface/state/apply-vrf-selection-policy: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-pkts: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-octets: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/state/dscp-set: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv6/state/dscp-set: +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` diff --git a/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/metadata.textproto b/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/metadata.textproto new file mode 100644 index 00000000000..133da2316dc --- /dev/null +++ b/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/metadata.textproto @@ -0,0 +1,18 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "db187faa-0f6d-44f8-86d2-832df9c285fc" +plan_id: "PF-1.6" +description: "Policy based VRF selection for IPV4/IPV6" +testbed: TESTBED_DUT_ATE_2LINKS + +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + } +} +tags: TAGS_AGGREGATION diff --git a/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/policy_based_vrf_selection_for_ipv4_ipv6_test.go b/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/policy_based_vrf_selection_for_ipv4_ipv6_test.go new file mode 100644 index 00000000000..61f2d893fba --- /dev/null +++ b/feature/policy_forwarding/vrf_selection/otg_tests/vrf_selection/policy_based_vrf_selection_for_ipv4_ipv6_test.go @@ -0,0 +1,507 @@ +package vrf_selection_test + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + dutDefaultAS = 65000 + dutNonDefaultAS = 65001 + ateAS1 = 65002 + ateAS2 = 65003 + vrfSelectionPolicyName = "pf16-vrf-selection" + IPv4 = "IPv4" + IPv6 = "IPv6" + trafficFrameSize = 256 + trafficRatePps = 100 + noOfPackets = 1000 + ipv4PrefixLen = 24 + ipv6PrefixLen = 64 + ipv4Prefix1 = "50.1.1.0" + ipv4Prefix2 = "60.1.1.0" + ipv6Prefix3 = "2050:db8:1::0" + ipv6Prefix4 = "2060:db8:2::0" + bgpWaitTime = 1 * time.Minute + routeTimeout = 15 * time.Second + trafficTimeout = 30 * time.Second + routeCount = 1 + ingress = false + egress = true + pktCountErrorPct = 0.03 // 3% error tolerance for packet counts +) + +var ( + dutPort1 = attrs.Attributes{ + Name: "port1", + Desc: "DUT Port1", + IPv4: "192.0.2.1", + IPv4Len: 30, + IPv6: "2001:db8:1::1", + IPv6Len: 64, + } + dutPort2 = attrs.Attributes{ + Name: "port2", + Desc: "DUT Port2", + IPv4: "192.0.2.5", + IPv4Len: 30, + IPv6: "2001:db8:2::1", + IPv6Len: 64, + } + dutPort3 = attrs.Attributes{ + Name: "port3", + Desc: "DUT Port3", + IPv4: "192.0.2.9", + IPv4Len: 30, + IPv6: "2001:db8:3::1", + IPv6Len: 64, + } + + ate1Port1 = attrs.Attributes{ + Name: "port1", + Desc: "ATE1 Port1", + IPv4: "192.0.2.2", + IPv4Len: 30, + IPv6: "2001:db8:1::2", + IPv6Len: 64, + MAC: "00:01:12:00:00:01", + } + ate2Port1 = attrs.Attributes{ + Name: "port2", + Desc: "ATE2 Port1", + IPv4: "192.0.2.6", + IPv4Len: 30, + IPv6: "2001:db8:2::2", + IPv6Len: 64, + MAC: "00:01:12:00:00:02", + } + ate2Port2 = attrs.Attributes{ + Name: "port3", + Desc: "ATE2 Port2", + IPv4: "192.0.2.10", + IPv4Len: 30, + IPv6: "2001:db8:3::2", + IPv6Len: 64, + MAC: "00:01:12:00:00:03", + } + + routesToAdvertise = map[string]string{ + fmt.Sprintf("%s/%d", ipv4Prefix1, ipv4PrefixLen): IPv4, + fmt.Sprintf("%s/%d", ipv4Prefix2, ipv4PrefixLen): IPv4, + fmt.Sprintf("%s/%d", ipv6Prefix3, ipv6PrefixLen): IPv6, + fmt.Sprintf("%s/%d", ipv6Prefix4, ipv6PrefixLen): IPv6, + } + + dutInterfaceMap = map[string]bool{} + + interfaceCounterPackets = map[string]uint64{} + niToInterfaceMap = map[string]string{} + + pktCountError = uint64(pktCountErrorPct * noOfPackets) + + sourcePort = ate1Port1 + defaultDestinationPort = ate2Port1 + nonDefaultDestinationPort = ate2Port2 + nonDefaultNIName = "NonDefaultNI" + defaultNIName = "" +) + +type testCase struct { + name string + vrfRules []cfgplugins.VrfRule +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestPolicyBasedVRFSelection(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + otg := ate.OTG() + defaultNIName = deviations.DefaultNetworkInstance(dut) + + configureDUT(t, dut) + config := configureATE(t, ate) + + otg.PushConfig(t, config) + otg.StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), config, IPv4) + otgutils.WaitForARP(t, ate.OTG(), config, IPv6) + + verifyBGP(t, dut, ate1Port1, defaultNIName) + verifyBGP(t, dut, ate2Port1, defaultNIName) + verifyBGP(t, dut, ate2Port2, nonDefaultNIName) + + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + niToInterfaceMap[defaultNIName] = dp2.Name() + niToInterfaceMap[nonDefaultNIName] = dp3.Name() + + verifyRoutes(t, dut, defaultNIName) + verifyRoutes(t, dut, nonDefaultNIName) + + testCases := []testCase{ + { + name: "PF-1.6.1: Default VRF for all flows with regular traffic profile", + vrfRules: []cfgplugins.VrfRule{ + {Index: 1, IpType: IPv4, SourcePrefix: ipv4Prefix1, PrefixLength: ipv4PrefixLen, NetInstName: defaultNIName}, + {Index: 2, IpType: IPv4, SourcePrefix: ipv4Prefix2, PrefixLength: ipv4PrefixLen, NetInstName: defaultNIName}, + {Index: 3, IpType: IPv6, SourcePrefix: ipv6Prefix3, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + {Index: 4, IpType: IPv6, SourcePrefix: ipv6Prefix4, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + }, + }, + { + name: "PF-1.6.2: Traffic from ATE1 to ATE2, 1 Prefix migrated to Non-Default VRF using the VRF selection policy", + vrfRules: []cfgplugins.VrfRule{ + {Index: 1, IpType: IPv4, SourcePrefix: ipv4Prefix1, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 2, IpType: IPv4, SourcePrefix: ipv4Prefix2, PrefixLength: ipv4PrefixLen, NetInstName: defaultNIName}, + {Index: 3, IpType: IPv6, SourcePrefix: ipv6Prefix3, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + {Index: 4, IpType: IPv6, SourcePrefix: ipv6Prefix4, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + }, + }, + { + name: "PF-1.6.3: Traffic from ATE1 to ATE2, 2 Prefixes migrated to Non-Default VRF using the VRF selection policy", + vrfRules: []cfgplugins.VrfRule{ + {Index: 1, IpType: IPv4, SourcePrefix: ipv4Prefix1, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 2, IpType: IPv4, SourcePrefix: ipv4Prefix2, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 3, IpType: IPv6, SourcePrefix: ipv6Prefix3, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + {Index: 4, IpType: IPv6, SourcePrefix: ipv6Prefix4, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + }, + }, + { + name: "PF-1.6.4: Traffic from ATE1 to ATE2, 3 Prefixes migrated to Non-Default VRF using the VRF selection policy", + vrfRules: []cfgplugins.VrfRule{ + {Index: 1, IpType: IPv4, SourcePrefix: ipv4Prefix1, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 2, IpType: IPv4, SourcePrefix: ipv4Prefix2, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 3, IpType: IPv6, SourcePrefix: ipv6Prefix3, PrefixLength: ipv6PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 4, IpType: IPv6, SourcePrefix: ipv6Prefix4, PrefixLength: ipv6PrefixLen, NetInstName: defaultNIName}, + }, + }, + { + name: "PF-1.6.5: Traffic from ATE1 to ATE2, 4 Prefixes migrated to Non-Default VRF using the VRF selection policy", + vrfRules: []cfgplugins.VrfRule{ + {Index: 1, IpType: IPv4, SourcePrefix: ipv4Prefix1, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 2, IpType: IPv4, SourcePrefix: ipv4Prefix2, PrefixLength: ipv4PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 3, IpType: IPv6, SourcePrefix: ipv6Prefix3, PrefixLength: ipv6PrefixLen, NetInstName: nonDefaultNIName}, + {Index: 4, IpType: IPv6, SourcePrefix: ipv6Prefix4, PrefixLength: ipv6PrefixLen, NetInstName: nonDefaultNIName}, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + runTest(t, dut, tc, ate, config) + }) + } +} + +func configureFlows(t *testing.T, config *gosnappi.Config, tc testCase) { + (*config).Flows().Clear() + for _, rule := range tc.vrfRules { + var destinationPort attrs.Attributes + switch rule.NetInstName { + case defaultNIName: + destinationPort = defaultDestinationPort + case nonDefaultNIName: + destinationPort = nonDefaultDestinationPort + default: + t.Fatalf("Invalid VRF name %s in rule %d", rule.NetInstName, rule.Index) + } + flowName := fmt.Sprintf("%sPrefix%d", rule.IpType, rule.Index) + flow := (*config).Flows().Add().SetName(flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{fmt.Sprintf("%s.%s", sourcePort.Name, rule.IpType)}).SetRxNames([]string{fmt.Sprintf("%s.%s", destinationPort.Name, rule.IpType)}) + flow.Size().SetFixed(trafficFrameSize) + flow.Rate().SetPps(trafficRatePps) + flow.Duration().SetFixedPackets(gosnappi.NewFlowFixedPackets().SetPackets(noOfPackets)) + + eth := flow.Packet().Add().Ethernet() + eth.Src().SetValue(sourcePort.MAC) + + switch rule.IpType { + case IPv4: + ipv4 := flow.Packet().Add().Ipv4() + ipv4.Src().SetValue(rule.SourcePrefix) + ipv4.Dst().SetValue(destinationPort.IPv4) + case IPv6: + ipv6 := flow.Packet().Add().Ipv6() + ipv6.Src().SetValue(rule.SourcePrefix) + ipv6.Dst().SetValue(destinationPort.IPv6) + + default: + t.Errorf("Invalid traffic type %s", rule.IpType) + } + } +} + +func runTest(t *testing.T, dut *ondatra.DUTDevice, tc testCase, ate *ondatra.ATEDevice, config gosnappi.Config) { + otg := ate.OTG() + t.Logf("Configuring Policy Forwarding for testcase %s", tc.name) + interfaceName := dut.Port(t, "port1").Name() + gnmi.Delete(t, dut, gnmi.OC().NetworkInstance(defaultNIName).PolicyForwarding().Config()) + configureVrfSelectionPolicy(t, dut, defaultNIName, vrfSelectionPolicyName, interfaceName, tc.vrfRules) + + configureFlows(t, &config, tc) + + otg.PushConfig(t, config) + otg.StartProtocols(t) + getInterfaceCounterValues(t, dut) + + otg.StartTraffic(t) + for _, fc := range config.Flows().Items() { + waitForTraffic(t, otg, fc.Name(), trafficTimeout) + } + verifyDutInterfaceCounters(t, dut, tc) + verifyFlowStatistics(t, ate, config) +} + +func configureVrfSelectionPolicy(t *testing.T, dut *ondatra.DUTDevice, niName, policyName, interfaceName string, vrfRules []cfgplugins.VrfRule) { + _, ni, pf := cfgplugins.SetupPolicyForwardingInfraOC(niName) + cfgplugins.ConfigureVrfSelectionPolicy(t, dut, pf, policyName, vrfRules) + cfgplugins.ApplyVrfSelectionPolicyToInterfaceOC(t, pf, interfaceName, policyName) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(niName).Config(), ni) +} + +func verifyFlowStatistics(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) { + otg := ate.OTG() + + otgutils.LogFlowMetrics(t, otg, config) + otgutils.LogPortMetrics(t, otg, config) + + for _, fc := range config.Flows().Items() { + flowMetrics := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(fc.Name()).State()) + if *flowMetrics.Counters.OutPkts == 0 { + t.Errorf("No packets transmitted") + } + if *flowMetrics.Counters.InPkts != noOfPackets { + t.Errorf("Flow %s received %d packets, expected %d packets", fc.Name(), *flowMetrics.Counters.InPkts, noOfPackets) + } + } +} + +func waitForTraffic(t *testing.T, otg *otg.OTG, flowName string, timeout time.Duration) { + transmitPath := gnmi.OTG().Flow(flowName).Transmit().State() + _, ok := gnmi.Watch(t, otg, transmitPath, timeout, func(val *ygnmi.Value[bool]) bool { + transmitState, present := val.Val() + return present && !transmitState + }).Await(t) + + if !ok { + t.Errorf("Traffic for flow %s did not stop within the timeout of %d", flowName, timeout) + } else { + t.Logf("Traffic for flow %s has stopped", flowName) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + dutInterfaceMap[dp1.Name()] = ingress + dutInterfaceMap[dp2.Name()] = egress + dutInterfaceMap[dp3.Name()] = egress + + t.Logf("Configuring Hardware Init") + configureHardwareInit(t, dut) + + cfgplugins.EnableDefaultNetworkInstanceBgp(t, dut, dutDefaultAS) + isDefaultVrf := true + t.Logf("Configuring Network Instances") + defaultNI := cfgplugins.ConfigureNetworkInstance(t, dut, defaultNIName, isDefaultVrf) + nonDefaultNI := cfgplugins.ConfigureNetworkInstance(t, dut, nonDefaultNIName, !isDefaultVrf) + + t.Logf("Configuring BGP") + cfgplugins.ConfigureBGPNeighbor(t, dut, defaultNI, dutPort1.IPv4, ate1Port1.IPv4, dutDefaultAS, dutDefaultAS, IPv4, true) + cfgplugins.ConfigureBGPNeighbor(t, dut, defaultNI, dutPort2.IPv4, ate2Port1.IPv4, dutDefaultAS, ateAS1, IPv4, true) + cfgplugins.ConfigureBGPNeighbor(t, dut, defaultNI, dutPort2.IPv4, ate2Port1.IPv6, dutDefaultAS, ateAS1, IPv6, true) + cfgplugins.ConfigureBGPNeighbor(t, dut, nonDefaultNI, dutPort3.IPv4, ate2Port2.IPv4, dutNonDefaultAS, ateAS2, IPv4, true) + cfgplugins.ConfigureBGPNeighbor(t, dut, nonDefaultNI, dutPort3.IPv4, ate2Port2.IPv6, dutNonDefaultAS, ateAS2, IPv6, true) + + cfgplugins.UpdateNetworkInstanceOnDut(t, dut, defaultNIName, defaultNI) + cfgplugins.UpdateNetworkInstanceOnDut(t, dut, nonDefaultNIName, nonDefaultNI) + + t.Logf("Configuring Interfaces") + configureDUTPort(t, dut, &dutPort1, dp1, defaultNIName) + configureDUTPort(t, dut, &dutPort2, dp2, defaultNIName) + configureDUTPort(t, dut, &dutPort3, dp3, nonDefaultNIName) +} + +func configureHardwareInit(t *testing.T, dut *ondatra.DUTDevice) { + hardwareInitCfg := cfgplugins.NewDUTHardwareInit(t, dut, cfgplugins.FeatureVrfSelectionExtended) + if hardwareInitCfg == "" { + return + } + cfgplugins.PushDUTHardwareInitConfig(t, dut, hardwareInitCfg) +} + +func configureDUTPort(t *testing.T, dut *ondatra.DUTDevice, attrs *attrs.Attributes, p *ondatra.Port, niName string) { + d := gnmi.OC() + cfgplugins.AssignToNetworkInstance(t, dut, p.Name(), niName, 0) + i := attrs.NewOCInterface(p.Name(), dut) + gnmi.Replace(t, dut, d.Interface(p.Name()).Config(), i) +} + +func verifyBGP(t *testing.T, dut *ondatra.DUTDevice, otgPort attrs.Attributes, vrfName string) { + bgpPath := gnmi.OC().NetworkInstance(vrfName).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Bgp() + + t.Logf("Waiting for BGP session to be ESTABLISHED in %s", vrfName) + status, ok := gnmi.Watch(t, dut, bgpPath.Neighbor(otgPort.IPv4).SessionState().State(), bgpWaitTime, func(val *ygnmi.Value[oc.E_Bgp_Neighbor_SessionState]) bool { + state, present := val.Val() + return present && state == oc.Bgp_Neighbor_SessionState_ESTABLISHED + }).Await(t) + if !ok { + t.Fatalf("BGP in %s did not establish: %v", vrfName, status) + } +} + +func getInterfaceCounterValues(t *testing.T, dut *ondatra.DUTDevice) { + for intfName, direction := range dutInterfaceMap { + counterPath := gnmi.OC().Interface(intfName).Counters().State() + counterValues := gnmi.Get(t, dut, counterPath) + switch direction { + case ingress: + t.Logf("Interface %s in unicast packets: %d", intfName, counterValues.GetInUnicastPkts()) + interfaceCounterPackets[intfName] = counterValues.GetInUnicastPkts() + case egress: + t.Logf("Interface %s out unicast packets: %d", intfName, counterValues.GetOutUnicastPkts()) + interfaceCounterPackets[intfName] = counterValues.GetOutUnicastPkts() + } + } +} + +func verifyDutInterfaceCounters(t *testing.T, dut *ondatra.DUTDevice, tc testCase) { + totalFlowCount := len(tc.vrfRules) + var expectedPkts uint64 + expectedIngressPkts := uint64(totalFlowCount * noOfPackets) + expectedEgressPacketsPerVRF := make(map[string]uint64) + for _, rule := range tc.vrfRules { + expectedEgressPacketsPerVRF[niToInterfaceMap[rule.NetInstName]] += uint64(noOfPackets) + } + + for intfName, direction := range dutInterfaceMap { + counterPath := gnmi.OC().Interface(intfName).Counters().State() + counterValues := gnmi.Get(t, dut, counterPath) + var packetType string + var total uint64 + switch direction { + case ingress: + t.Logf("Verifying ingress counters for interface %s", intfName) + total = counterValues.GetInUnicastPkts() + packetType = "in-unicast-pkts" + expectedPkts = expectedIngressPkts + case egress: + t.Logf("Verifying egress counters for interface %s", intfName) + total = counterValues.GetOutUnicastPkts() + packetType = "out-unicast-pkts" + expectedPkts = expectedEgressPacketsPerVRF[intfName] + } + + got := total - interfaceCounterPackets[intfName] + message := fmt.Sprintf("Interface %s %s: got %d, want %d ± %d", intfName, packetType, got, expectedPkts, pktCountError) + if -pktCountError < got-expectedPkts || got-expectedPkts > pktCountError { + t.Errorf("Error: %s", message) + } else { + t.Log(message) + } + interfaceCounterPackets[intfName] = total + } +} + +func verifyRoutes(t *testing.T, dut *ondatra.DUTDevice, vrfName string) { + for route, ipType := range routesToAdvertise { + var ok bool + switch ipType { + case IPv4: + aftVrf1 := gnmi.OC().NetworkInstance(vrfName).Afts().Ipv4Entry(route) + _, ok = gnmi.Watch(t, dut, aftVrf1.State(), routeTimeout, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv4Entry]) bool { + return val.IsPresent() + }).Await(t) + case IPv6: + route = strings.Replace(route, "::0/", "::/", 1) + aftVrf1 := gnmi.OC().NetworkInstance(vrfName).Afts().Ipv6Entry(route) + _, ok = gnmi.Watch(t, dut, aftVrf1.State(), routeTimeout, func(val *ygnmi.Value[*oc.NetworkInstance_Afts_Ipv6Entry]) bool { + return val.IsPresent() + }).Await(t) + } + if !ok { + t.Errorf("Route %s is not installed in AFT for %s", route, vrfName) + } else { + t.Logf("Route %s successfully installed in AFT for %s", route, vrfName) + } + } +} + +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + config := gosnappi.NewConfig() + + p1 := ate.Port(t, "port1") + p2 := ate.Port(t, "port2") + p3 := ate.Port(t, "port3") + + d1 := ate1Port1.AddToOTG(config, p1, &dutPort1) + d2 := ate2Port1.AddToOTG(config, p2, &dutPort2) + d3 := ate2Port2.AddToOTG(config, p3, &dutPort3) + + ip1 := d1.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + ip2 := d2.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + ip2v6 := d2.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + ip3 := d3.Ethernets().Items()[0].Ipv4Addresses().Items()[0] + ip3v6 := d3.Ethernets().Items()[0].Ipv6Addresses().Items()[0] + + bgp1 := d1.Bgp().SetRouterId(ate1Port1.IPv4) + bgp1Peer := bgp1.Ipv4Interfaces().Add().SetIpv4Name(ip1.Name()).Peers().Add().SetName(fmt.Sprintf("%s.BGP.peer", d1.Name())) + bgp1Peer.SetPeerAddress(dutPort1.IPv4).SetAsNumber(uint32(dutDefaultAS)).SetAsType(gosnappi.BgpV4PeerAsType.IBGP) + + bgp2 := d2.Bgp().SetRouterId(ate2Port1.IPv4) + bgp2Peer := bgp2.Ipv4Interfaces().Add().SetIpv4Name(ip2.Name()).Peers().Add().SetName(fmt.Sprintf("%s.v4.BGP.peer", d2.Name())) + bgp2Peer.SetPeerAddress(dutPort2.IPv4).SetAsNumber(uint32(ateAS1)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + bgp2Netv4 := bgp2Peer.V4Routes().Add().SetName("vrf-p2-v4-routes") + bgp2Netv4.SetNextHopIpv4Address(ip2.Address()) + bgp2Netv4.Addresses().Add().SetAddress(ipv4Prefix1).SetPrefix(ipv4PrefixLen).SetCount(routeCount) + bgp2Netv4.Addresses().Add().SetAddress(ipv4Prefix2).SetPrefix(ipv4PrefixLen).SetCount(routeCount) + + bgp2Peerv6 := bgp2.Ipv6Interfaces().Add().SetIpv6Name(ip2v6.Name()).Peers().Add().SetName(fmt.Sprintf("%s.v6.BGP.peer", d2.Name())) + bgp2Peerv6.SetPeerAddress(dutPort2.IPv6).SetAsNumber(uint32(ateAS1)).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + bgp2Netv6 := bgp2Peerv6.V6Routes().Add().SetName("vrf-p2-v6-routes") + bgp2Netv6.SetNextHopIpv6Address(ip2v6.Address()) + bgp2Netv6.Addresses().Add().SetAddress(ipv6Prefix3).SetPrefix(ipv6PrefixLen).SetCount(routeCount) + bgp2Netv6.Addresses().Add().SetAddress(ipv6Prefix4).SetPrefix(ipv6PrefixLen).SetCount(routeCount) + + bgp3 := d3.Bgp().SetRouterId(ate2Port2.IPv4) + bgp3Peer := bgp3.Ipv4Interfaces().Add().SetIpv4Name(ip3.Name()).Peers().Add().SetName(fmt.Sprintf("%s.v4.BGP.peer", d3.Name())) + bgp3Peer.SetPeerAddress(dutPort3.IPv4).SetAsNumber(uint32(ateAS2)).SetAsType(gosnappi.BgpV4PeerAsType.EBGP) + + bgp3Netv4 := bgp3Peer.V4Routes().Add().SetName("vrf-p3-v4-routes") + bgp3Netv4.SetNextHopIpv4Address(ip3.Address()) + bgp3Netv4.Addresses().Add().SetAddress(ipv4Prefix1).SetPrefix(ipv4PrefixLen).SetCount(routeCount) + bgp3Netv4.Addresses().Add().SetAddress(ipv4Prefix2).SetPrefix(ipv4PrefixLen).SetCount(routeCount) + + bgp3Peerv6 := bgp3.Ipv6Interfaces().Add().SetIpv6Name(ip3v6.Name()).Peers().Add().SetName(fmt.Sprintf("%s.v6.BGP.peer", d3.Name())) + bgp3Peerv6.SetPeerAddress(dutPort3.IPv6).SetAsNumber(uint32(ateAS2)).SetAsType(gosnappi.BgpV6PeerAsType.EBGP) + + bgp3Netv6 := bgp3Peerv6.V6Routes().Add().SetName("vrf-p3-v6-routes") + bgp3Netv6.SetNextHopIpv6Address(ip3v6.Address()) + bgp3Netv6.Addresses().Add().SetAddress(ipv6Prefix3).SetPrefix(ipv6PrefixLen).SetCount(routeCount) + bgp3Netv6.Addresses().Add().SetAddress(ipv6Prefix4).SetPrefix(ipv6PrefixLen).SetCount(routeCount) + + return config +} diff --git a/feature/qos/ecn/feature.textproto b/feature/qos/ecn/feature.textproto deleted file mode 100644 index b76984b3c87..00000000000 --- a/feature/qos/ecn/feature.textproto +++ /dev/null @@ -1,53 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "qos_ecn" - version: 1 -} - -# Queue management -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/min-threshold" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/state/min-threshold" -} -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-threshold" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-threshold" -} -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/enable-ecn" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/state/enable-ecn" -} -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/weight" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/state/weight" -} -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/drop" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/state/drop" -} -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/max-drop-probability-percent" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/state/max-drop-probability-percent" -} - -# Interface -config_path { - path: "/qos/interfaces/interface/output/queues/queue/config/queue-management-profile" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/queue-management-profile" -} diff --git a/feature/qos/ecn/ate_tests/dscp_ecn_encap_decap_test/README.md b/feature/qos/ecn/otg_tests/dscp_ecn_encap_decap_test/README.md similarity index 56% rename from feature/qos/ecn/ate_tests/dscp_ecn_encap_decap_test/README.md rename to feature/qos/ecn/otg_tests/dscp_ecn_encap_decap_test/README.md index 42818c36f8b..59ad661969d 100644 --- a/feature/qos/ecn/ate_tests/dscp_ecn_encap_decap_test/README.md +++ b/feature/qos/ecn/otg_tests/dscp_ecn_encap_decap_test/README.md @@ -105,57 +105,64 @@ decap process. * BE1 * BE0 -## Config parameter coverage +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config parameter coverage + + ## Classifiers + /qos/classifiers/classifier/config/name: + /qos/classifiers/classifier/config/type: + /qos/classifiers/classifier/terms/term/actions/config/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/config/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/config/dscp-set: + /qos/classifiers/classifier/terms/term/config/id: + + ## Forwarding Groups + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + + ## Queue + /qos/queues/queue/config/name: + + ## Interfaces + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/output/queues/queue/config/name: + /qos/interfaces/interface/output/scheduler-policy/config/name: + /qos/interfaces/interface/output/queues/queue/config/queue-management-profile: + + ## Scheduler policy + /qos/scheduler-policies/scheduler-policy/config/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/priority: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/sequence: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/id: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/input-type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/queue: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/weight: + + ## ECN + /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: + + ## Telemetry parameter coverage + + /qos/interfaces/interface/output/queues/queue/state/transmit-pkts: + /qos/interfaces/interface/output/queues/queue/state/transmit-octets: + /qos/interfaces/interface/output/queues/queue/state/dropped-pkts: + /qos/interfaces/interface/output/queues/queue/state/dropped-octets: + + ## Protocol/RPC Parameter Coverage +rpcs: + gnmi: + gNMI.Get: + gNMI.Set: +``` -* Classifiers - - * /qos/classifiers/classifier/config/name - * /qos/classifiers/classifier/config/type - * /qos/classifiers/classifier/terms/term/actions/config/target-group - * /qos/classifiers/classifier/terms/term/conditions/ipv4/config/dscp-set - * qos/classifiers/classifier/terms/term/conditions/ipv6/config/dscp-set - * /qos/classifiers/classifier/terms/term/config/id - -* Forwarding Groups - - * /qos/forwarding-groups/forwarding-group/config/name - * /qos/forwarding-groups/forwarding-group/config/output-queue - -* Queue - - * /qos/queues/queue/config/name - -* Interfaces - - * /qos/interfaces/interface/input/classifiers/classifier/config/name - * /qos/interfaces/interface/output/queues/queue/config/name - * /qos/interfaces/interface/output/scheduler-policy/config/name - * /qos/interfaces/interface/output/queues/queue/config/queue-management-profile - -* Scheduler policy - - * /qos/scheduler-policies/scheduler-policy/config/name - * /qos/scheduler-policies/scheduler - -policy/schedulers/scheduler/config/priority - * /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/sequence - * /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/type - * /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/id - * /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/input-type - * /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/queue - * /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/weight - -* ECN - - * 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 - -## Telemetry parameter coverage - -* /qos/interfaces/interface/output/queues/queue/state/transmit-pkts -* /qos/interfaces/interface/output/queues/queue/state/transmit-octets -* /qos/interfaces/interface/output/queues/queue/state/dropped-pkts -* /qos/interfaces/interface/output/queues/queue/state/dropped-octets diff --git a/feature/qos/ecn/otg_tests/dscp_ecn_encap_decap_test/metadata.textproto b/feature/qos/ecn/otg_tests/dscp_ecn_encap_decap_test/metadata.textproto new file mode 100644 index 00000000000..f70ebe6664d --- /dev/null +++ b/feature/qos/ecn/otg_tests/dscp_ecn_encap_decap_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "6a39bcc0-b989-4e12-b709-60316bc916c0" +plan_id: "DP-1.13" +description: "DSCP and ECN bits are copied over during IPinIP encap and decap" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/qos/feature.textproto b/feature/qos/feature.textproto deleted file mode 100644 index 4e439a3762d..00000000000 --- a/feature/qos/feature.textproto +++ /dev/null @@ -1,305 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "qos" - version: 1 -} - -# Classifiers -config_path { - path: "/qos/classifiers/classifier/config/name" -} -telemetry_path { - path: "/qos/classifiers/classifier/state/name" -} -config_path { - path: "/qos/classifiers/classifier/config/type" -} -telemetry_path { - path: "/qos/classifiers/classifier/state/type" -} -config_path { - path: "/qos/classifiers/classifier/terms/term/actions/config/target-group" -} -telemetry_path { - path: "/qos/classifiers/classifier/terms/term/actions/state/target-group" -} -config_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv4/config/dscp" -} -telemetry_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp" -} -config_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv4/config/dscp-set" -} -telemetry_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv4/state/dscp-set" -} -config_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv6/config/dscp" -} -telemetry_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp" -} -config_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv6/config/dscp-set" -} -telemetry_path { - path: "/qos/classifiers/classifier/terms/term/conditions/ipv6/state/dscp-set" -} -config_path { - path: "/qos/classifiers/classifier/terms/term/config/id" -} -telemetry_path { - path: "/qos/classifiers/classifier/terms/term/state/id" -} - -# Forwarding Groups -config_path { - path: "/qos/forwarding-groups/forwarding-group/config/fabric-priority" -} -telemetry_path { - path: "/qos/forwarding-groups/forwarding-group/state/fabric-priority" -} -config_path { - path: "/qos/forwarding-groups/forwarding-group/config/name" -} -telemetry_path { - path: "/qos/forwarding-groups/forwarding-group/state/name" -} -config_path { - path: "/qos/forwarding-groups/forwarding-group/config/output-queue" -} -telemetry_path { - path: "/qos/forwarding-groups/forwarding-group/state/output-queue" -} - -# Interfaces -config_path { - path: "/qos/interfaces/interface/config/interface-id" -} -telemetry_path { - path: "/qos/interfaces/interface/state/interface-id" -} -config_path { - path: "/qos/interfaces/interface/input/classifiers/classifier/config/name" -} -telemetry_path { - path: "/qos/interfaces/interface/input/classifiers/classifier/state/name" -} -config_path { - path: "/qos/interfaces/interface/input/classifiers/classifier/config/type" -} -telemetry_path { - path: "/qos/interfaces/interface/input/classifiers/classifier/state/type" -} -config_path { - path: "/qos/interfaces/interface/output/queues/queue/config/name" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/name" -} -config_path { - path: "/qos/interfaces/interface/output/queues/queue/config/name" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/name" -} -config_path { - path: "/qos/interfaces/interface/output/queues/queue/config/queue-management-profile" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/queue-management-profile" -} -config_path { - path: "/qos/interfaces/interface/output/scheduler-policy/config/name" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/state/name" -} -telemetry_path { - path: "/qos/interfaces/interface/input/classifiers/classifier/terms/term/state/matched-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/classifiers/classifier/terms/term/state/matched-packets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/queues/queue/state/avg-queue-len" -} -telemetry_path { - path: "/qos/interfaces/interface/input/queues/queue/state/dropped-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/queues/queue/state/dropped-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/input/queues/queue/state/max-queue-len" -} -telemetry_path { - path: "/qos/interfaces/interface/input/queues/queue/state/transmit-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/queues/queue/state/transmit-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/violating-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/violating-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/avg-queue-len" -} -telemetry_path { - path: "/qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/dropped-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/dropped-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/max-queue-len" -} -telemetry_path { - path: "/qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/transmit-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/input/virtual-output-queues/voq-interface/queues/queue/state/transmit-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/output/classifiers/classifier/terms/term/state/matched-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/classifiers/classifier/terms/term/state/matched-packets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/avg-queue-len" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/dropped-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/dropped-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/max-queue-len" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/transmit-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/queues/queue/state/transmit-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/schedulers/scheduler/state/conforming-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/schedulers/scheduler/state/conforming-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/schedulers/scheduler/state/exceeding-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/schedulers/scheduler/state/exceeding-pkts" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/schedulers/scheduler/state/violating-octets" -} -telemetry_path { - path: "/qos/interfaces/interface/output/scheduler-policy/schedulers/scheduler/state/violating-pkts" -} - -# Queues -config_path { - path: "/qos/queues/queue/config/name" -} -telemetry_path { - path: "/qos/queues/queue/state/name" -} -config_path { - path: "/qos/queue-management-profiles/queue-management-profile/config/name" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/state/name" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/red/uniform/state/enable-ecn" -} -telemetry_path { - path: "/qos/queue-management-profiles/queue-management-profile/red/uniform/state/min-threshold" -} - -# Scheduler policy -config_path { - path: "/qos/scheduler-policies/scheduler-policy/config/name" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/state/name" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/priority" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/priority" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/sequence" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/sequence" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/type" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/state/type" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/id" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/id" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/input-type" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/input-type" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/queue" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/queue" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/config/weight" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/inputs/input/state/weight" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/cir" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/state/cir" -} -config_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/pir" -} -telemetry_path { - path: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/state/pir" -} diff --git a/feature/qos/otg_tests/ingress_police_default/README.md b/feature/qos/ingress_policer/otg_tests/ingress_police_default/README.md similarity index 100% rename from feature/qos/otg_tests/ingress_police_default/README.md rename to feature/qos/ingress_policer/otg_tests/ingress_police_default/README.md diff --git a/feature/qos/otg_tests/ingress_police_default/ingress_police_default_test.go b/feature/qos/ingress_policer/otg_tests/ingress_police_default/ingress_police_default_test.go similarity index 100% rename from feature/qos/otg_tests/ingress_police_default/ingress_police_default_test.go rename to feature/qos/ingress_policer/otg_tests/ingress_police_default/ingress_police_default_test.go diff --git a/feature/qos/otg_tests/ingress_police_default/metadata.textproto b/feature/qos/ingress_policer/otg_tests/ingress_police_default/metadata.textproto similarity index 100% rename from feature/qos/otg_tests/ingress_police_default/metadata.textproto rename to feature/qos/ingress_policer/otg_tests/ingress_police_default/metadata.textproto diff --git a/feature/qos/otg_tests/ingress_police_nhg/README.md b/feature/qos/ingress_policer/otg_tests/ingress_police_nhg/README.md similarity index 99% rename from feature/qos/otg_tests/ingress_police_nhg/README.md rename to feature/qos/ingress_policer/otg_tests/ingress_police_nhg/README.md index 989fe0f6657..5bc439ed5e8 100644 --- a/feature/qos/otg_tests/ingress_police_nhg/README.md +++ b/feature/qos/ingress_policer/otg_tests/ingress_police_nhg/README.md @@ -1,4 +1,4 @@ -# DP-2.2 QoS scheduler with 1 rate 2 color policer, classifying on next-hop group +# DP-2.2: QoS scheduler with 1 rate 2 color policer, classifying on next-hop group ## Summary @@ -383,6 +383,7 @@ NH#201 -> { * flow B have the same flow label * flow A and B labels do not match + #### OpenConfig Path and RPC Coverage ```yaml diff --git a/feature/qos/ingress_policer/otg_tests/ingress_police_nhg/metadata.textproto b/feature/qos/ingress_policer/otg_tests/ingress_police_nhg/metadata.textproto new file mode 100644 index 00000000000..ba9f3fef605 --- /dev/null +++ b/feature/qos/ingress_policer/otg_tests/ingress_police_nhg/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "89a10b5b-76e7-4ccc-a37f-c59b9a2ca884" +plan_id: "DP-2.2" +description: "QoS scheduler with 1 rate 2 color policer, classifying on next-hop group" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/README.md b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/README.md new file mode 100644 index 00000000000..f84017e7b92 --- /dev/null +++ b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/README.md @@ -0,0 +1,233 @@ +# DP-2.5: Police traffic on input matching all packets using 2 rate, 3 color marker + +## Summary + +Use IP address and mac-address from topology shared below. Static Routes can be used for this. +Configure an ingress scheduler to police traffic using a 2 rate, 3 color policer and attach the scheduler to the interface without a classifier. +Send traffic to validate the policer. + +## Topology + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Test setup + +```mermaid +graph LR; +ATE[ATE] <-- (Port 1) --> DUT[DUT] <-- (Port 2) --> ATE[ATE]; +``` + +## Procedure + +### Testbed setup - Generate configuration for ATE and DUT + +#### Source & Destination Port for traffic + +* ATE (Port1) --- IP Connectivity --- DUT (Dut1), DUT (Dut2) --- IP Connectivity --- ATE (Port2) +* Use below to configure traffic with following source and destination. + + * Dut1 = Attributes { + Desc: "Dut1", + MAC: "02:01:00:00:00:01", + IPv4: "200.0.0.1/24", + IPv6: "2001:f:d:e::1/126", + } + * atePort1 = Attributes{ + Desc: "atePort1", + MAC: "02:01:00:00:00:02", + Vlan: "100", + IPv4: "200.0.0.2/24", + IPv6: "2001:f:d:e::2/126", + } + * Dut2 = Attributes{ + Desc: "Dut2", + MAC: "02:00:01:01:01:01", + IPv4: "100.0.0.1/24", + IPv6: "2001:c:d:e::1/126", + } + * atePort2 = Attributes{ + Desc: "atePort2", + MAC: "02:00:01:01:01:02", + IPv4: "100.0.0.2/24", + IPv6: "2001:c:d:e::2/126", + } + +* Create static route from atePort1 to atePort2. + +### SetUp + +* Generate config for scheduler polices with an input rate 2Gbps limit +* Apply them to DUT interface . Dut1 is LAG in provided setup. +* Use gnmi.Replace to push the config to the DUT. + +### Canonical OC for DUT configuration + +The configuration required for the 2R3C policer with classifier is included below: + +```json +{ + "qos": { + # + # output queue (i.e. is QUEUE_3) + # + "queues": { + "queue": [ + { + "config": { + "name": "QUEUE_1" + }, + "name": "QUEUE_1" + }, + { + "config": { + "name": "QUEUE_2" + }, + "name": "QUEUE_2" + }, + { + "config": { + "name": "QUEUE_3" + }, + "name": "QUEUE_3" + } + ] + }, + # + # A single scheduler policy can be applied per interface. + # + "scheduler-policies": { + "scheduler-policy": [ + { + "config": { + "name": "group_A_2Gb" + }, + "name": "group_A_2Gb", + "schedulers": { + "scheduler": [ + { + "config": { + "sequence": 1, + "type": "TWO_RATE_THREE_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "my input policer 2Gb", + "input-type": "QUEUE", + "queue": "QUEUE_3" + }, + "id": "my input policer 2Gb" + } + ] + }, + "two-rate-three-color": { + "config": { + "cir": "1000000000", + "pir": "2000000000", + "bc": 100000, + "be": 100000, + "queuing-behavior": "POLICE" + }, + "exceed-action": { + "config": { + "drop": false + } + }, + "violate-action": { + "config": { + "drop": true + } + } + } + } + ] + } + } + ] + }, + # + # Interfaces input are mapped to the desired scheduler. + "interfaces": { + "interface": [ + { + "interface-id": "Dut1.100", + "config": { + "interface-id": "Dut1.100" + }, + "input": { + "scheduler-policy": { + "config": { + "name": "group_A_2Gb" + } + } + } + } + ] + } + } +} +``` + +### DP-2.5.1 Test traffic + +* Send traffic + * Send flow traffic from atePort1 to DUT towards atePort2 at 1.5Gbps (note cir is 1Gbps & pir is 2Gbps). + * Validate qos counters on dut1 of DUT . + * Validate DUT qos interface scheduler counters count packets as conforming-pkts, conforming-octets, exceeding-pkts & exceeding-octets. + * Validate packets are received by atePort2. + * Validate at OTG that 0 packets are lost on flow. + * Increase traffic on flow to atePort2 to 4Gbps + * Validate that flow to atePort2 experiences ~50% packet loss (+/- 1%) + * Validate packet loss count as violating-pkts & violating-octets. + + +#### OpenConfig Path and RPC Coverage + +```yaml +paths: + # qos scheduler config + /qos/scheduler-policies/scheduler-policy/config/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/cir: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/cir-pct: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/pir: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/pir-pct: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/bc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/be: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/conform-action/config/set-dscp: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/conform-action/config/set-dot1p: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/conform-action/config/set-mpls-tc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/set-dscp: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/set-dot1p: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/set-mpls-tc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/drop: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/set-dscp: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/set-dot1p: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/set-mpls-tc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/drop: + + # qos interfaces config + /qos/interfaces/interface/config/interface-id: + /qos/interfaces/interface/input/scheduler-policy/config/name: + + # qos interface scheduler counters + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-pkts: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-octets: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-pkts: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-octets: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/violating-pkts: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/violating-octets: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF diff --git a/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/metadata.textproto b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/metadata.textproto new file mode 100644 index 00000000000..f92ac27d3d1 --- /dev/null +++ b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c372d67b-67a7-4ce4-8c13-4930735a470a" +plan_id: "DP-2.5" +description: "Police traffic on input matching all packets using 2 rate, 3 color marker" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/README.md b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/README.md new file mode 100644 index 00000000000..5943825d7d4 --- /dev/null +++ b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/README.md @@ -0,0 +1,328 @@ +# DP-2.6: Police traffic on input matching all packets using 2 rate, 3 color marker with classifier + +## Summary + +Use IP address and mac-address from topology shared below. Static Routes can be used for this. +Configure an ingress scheduler to police traffic using a 2 rate, 3 color policer and attach the scheduler to the interface with classifier. +Lack of match conditions will cause all packets to be matched. +Send traffic to validate the policer. + +## Topology + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Test setup + +```mermaid +graph LR; +ATE[ATE] <-- (Port 1) --> DUT[DUT] <-- (Port 2) --> ATE[ATE]; +``` + +## Procedure + +### Testbed setup - Generate configuration for ATE and DUT + +#### Source & Destination Port for traffic + +* ATE (Port1) --- IP Connectivity --- DUT (Dut1), DUT (Dut2) --- IP Connectivity --- ATE (Port2) +* Use below to configure traffic with following source and destination. + + * Dut1 = Attributes { + Desc: "Dut1", + MAC: "02:01:00:00:00:01", + IPv4: "200.0.0.1/24", + IPv6: "2001:f:d:e::1/126", + } + * atePort1 = Attributes{ + Desc: "atePort1", + MAC: "02:01:00:00:00:02", + IPv4: "200.0.0.2/24", + IPv6: "2001:f:d:e::2/126", + } + * Dut2 = Attributes{ + Desc: "Dut2", + MAC: "02:00:01:01:01:01", + IPv4: "100.0.0.1/24", + IPv6: "2001:c:d:e::1/126", + } + * atePort2 = Attributes{ + Desc: "atePort2", + MAC: "02:00:01:01:01:02", + IPv4: "100.0.0.2/24", + IPv6: "2001:c:d:e::2/126", + } + +* Create static route from atePort1 to atePort2. + +### SetUp + +* Generate config for scheduler polices with an input rate 2Gbps limit and a classifier. +* Apply them to DUT interface . Dut1 is LAG in provided setup. +* Use gnmi.Replace to push the config to the DUT. + +### Canonical OC for DUT configuration + +The configuration required for the 2R3C policer with classifier is included below: + +```json +{ + "qos": { + "classifiers": { + # + # The specification for the classifier to be applied to an interface. + # The classifier is applied to IPv4 packets. + # + "classifier": [ + { + "config": { + "name": "group_A_2Gb", + "type": "IPV4" + }, + "name": "group_A_2Gb", + # + # The terms that are present in the classifier. + # If no condition is provided in term , then everything will be considered as matched. + # + "terms": { + "term": [ + { + # As condition container is absent from term , so no match criteria for + # term in the classifier, so it should be interpreted as match all condition. + "actions": { + "config": { + # + # Packets matching this term (i.e., is class-default + # as specified below) are grouped into the 'TRAFFIC_CLASS_3' + # forwarding-group. + # + "target-group": "TRAFFIC_CLASS_3" + } + }, + "config": { + "id": "class-default" + }, + "id": "class-default" + } + ] + } + } + ] + }, + # + # The definition of the forwarding groups. Each forwarding + # group has a name, and an output queue. + # + "forwarding-groups": { + "forwarding-group": [ + { + "config": { + "name": "TRAFFIC_CLASS_3", + "output-queue": "QUEUE_3" + }, + "name": "TRAFFIC_CLASS_3" + } + ] + }, + # + # A forwarding-group is mapped to an output queue (i.e. is QUEUE_3) + # + "queues": { + "queue": [ + { + "config": { + "name": "QUEUE_1" + }, + "name": "QUEUE_1" + }, + { + "config": { + "name": "QUEUE_2" + }, + "name": "QUEUE_2" + }, + { + "config": { + "name": "QUEUE_3" + }, + "name": "QUEUE_3" + } + ] + }, + # + # A single scheduler policy can be applied per interface. + # + "scheduler-policies": { + "scheduler-policy": [ + { + "config": { + "name": "group_A_2Gb" + }, + "name": "group_A_2Gb", + "schedulers": { + "scheduler": [ + { + "config": { + "sequence": 1, + "type": "TWO_RATE_THREE_COLOR_with_CLASSIFIER" + }, + "inputs": [ + { + "input": "my input policer 2Gb", + "config": { + "id": "my input policer 2Gb", + "input-type": "QUEUE", + "queue": "QUEUE_3" + } + } + ], + "two-rate-three-color": { + "config": { + "cir": "1000000000", + "pir": "2000000000", + "bc": 100000, + "be": 100000, + "queuing-behavior": "POLICE" + }, + "exceed-action": { + "config": { + "drop": false + } + }, + "violate-action": { + "config": { + "drop": true + } + } + } + } + ] + } + } + ] + }, + # + # For configuration, the interfaces container specifies the + # binding between the specified classifiers,schedulers and + # an interface. + # Interfaces input are mapped to the desired scheduler and classifier. + # + "interfaces": { + "interface": [ + { + "interface-id": "Dut1", + "config": { + "interface-id": "Dut1" + }, + # + # An input classifier is applied to the interface by + # referencing the classifier name within the /qos/interfaces + # list. + # + "input": { + "classifers": { + "classifier": [ + { + "config": { + "name": "group_A_2Gb", + "type": "IPV4" + }, + "type": "IPV4" + } + ] + }, + "scheduler-policy": { + "config": { + "name": "group_A_2Gb" + } + } + } + } + ] + } + } +} +``` + +### DP-2.6.1 Test traffic + +* Send traffic + * Send flow traffic from atePort1 to DUT towards atePort2 at 1.5Gbps (note cir is 1Gbps & pir is 2Gbps). + * Validate qos counters on dut1 of DUT . + * Validate DUT qos interface scheduler counters count packets as conforming-pkts, conforming-octets, exceeding-pkts & exceeding-octets. + * Validate packets are received by atePort2. + * Validate at OTG that 0 packets are lost on flow. + * Increase traffic on flow to atePort2 to 4Gbps + * Validate that flow to atePort2 experiences ~50% packet loss (+/- 1%) + * Validate packet loss count as violating-pkts & violating-octets. + + +#### OpenConfig Path and RPC Coverage + +```yaml +paths: + # qos scheduler-policies config + /qos/scheduler-policies/scheduler-policy/config/name: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/config/type: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/cir: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/cir-pct: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/pir: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/pir-pct: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/bc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/config/be: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/conform-action/config/set-dscp: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/conform-action/config/set-dot1p: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/conform-action/config/set-mpls-tc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/set-dscp: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/set-dot1p: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/set-mpls-tc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/exceed-action/config/drop: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/set-dscp: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/set-dot1p: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/set-mpls-tc: + /qos/scheduler-policies/scheduler-policy/schedulers/scheduler/two-rate-three-color/violate-action/config/drop: + + # qos classifier config path + /qos/classifiers/classifier/config/name: + /qos/classifiers/classifier/config/type: + /qos/classifiers/classifier/terms/term/config/id: + /qos/classifiers/classifier/terms/term/actions/config/target-group: + + # qos forwarding-group config path + /qos/forwarding-groups/forwarding-group/config/name: + /qos/forwarding-groups/forwarding-group/config/output-queue: + + # qos queue config path + /qos/queues/queue/config/name: + /qos/queues/queue/config/queue-id: + + + # qos interfaces config + /qos/interfaces/interface/config/interface-id: + /qos/interfaces/interface/input/scheduler-policy/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/type: + + # qos interface scheduler state path + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-pkts: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/conforming-octets: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-pkts: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/exceeding-octets: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/violating-pkts: + /qos/interfaces/interface/input/scheduler-policy/schedulers/scheduler/state/violating-octets: + + # qos interface classifier state path + /qos/interfaces/interface/input/classifiers/classifier/terms/term/state/matched-packets: + /qos/interfaces/interface/input/classifiers/classifier/terms/term/state/matched-octets: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* FFF diff --git a/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/metadata.textproto b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/metadata.textproto new file mode 100644 index 00000000000..75e903dba59 --- /dev/null +++ b/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "d8e552be-5a50-4fae-8c2a-5bcbffdfb9bd" +plan_id: "DP-2.6" +description: "Police traffic on input matching all packets using 2 rate, 3 color marker with classifier" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/qos/otg_tests/Ingress_ARP_policing/ingress_arp_policing_test.go b/feature/qos/otg_tests/Ingress_ARP_policing/ingress_arp_policing_test.go new file mode 100644 index 00000000000..98a3c99373c --- /dev/null +++ b/feature/qos/otg_tests/Ingress_ARP_policing/ingress_arp_policing_test.go @@ -0,0 +1,358 @@ +// 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 ingress_arp_policing_test + +import ( + "context" + "fmt" + "math" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygnmi/ygnmi" +) + +const ( + ipv4PrefixLen = 30 + trafficDuration = 30 * time.Second + rateKbps = 1500 + fixedSize = 64 + fixedTotalPkts = 20000 + expectedLoss = true + cir = 1000 + burstCount = 100 + classifierName = "ARP-match" + newTermName = "ARP" + ethType = 2054 + groupName = "arp-policer" + SchedulerName = "ARP-policer" + flowName = "arp-test" + tolerance = 5 + macB = "ff:ff:ff:ff:ff:ff" +) + +var ( + dutPort1 = attrs.Attributes{ + Desc: "port1", + MAC: "02:01:00:00:00:01", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + } + otgPort1 = attrs.Attributes{ + Name: "port1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv4Len: ipv4PrefixLen, + } + dutPort2 = attrs.Attributes{ + Desc: "port2", + MAC: "02:01:00:00:00:02", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + } + otgPort2 = attrs.Attributes{ + Name: "port2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv4Len: ipv4PrefixLen, + } + timeout = 1 * time.Minute +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestIngressArpPolicing(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + configureDUT(t, dut) + config := configureATE(t, ate) + verifyPortsUp(t, dut.Device) + addFlow(t, config) + ate.OTG().PushConfig(t, config) + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), config, "IPv4") + t.Run("VerifyQoSCounters", func(t *testing.T) { + t.Log("Validating traffic without policy-map") + txRateKbps, rxRateKbps := startAndMeasureTraffic(t, ate, trafficDuration) + validateRates(t, txRateKbps, rxRateKbps, txRateKbps, tolerance, "Without policy-map") + verifyTrafficAndLog(t, "QoS Counters", verifyPortTraffic(t, ate, config)) + }) + t.Run("VerifyTransmitOctetsWithCIR", func(t *testing.T) { + t.Log("Validating traffic with policy-map") + configureDUTTrafficPolicy(t, dut, dut.Port(t, "port1").Name()) + txRateKbps, rxRateKbps := startAndMeasureTraffic(t, ate, trafficDuration) + validateRates(t, txRateKbps, rxRateKbps, float64(cir), tolerance, "With CIR restriction") + verifyTrafficAndLog(t, "Transmit/Octets and Loss", verifyPortTrafficwithCir(t, ate, config)) + }) +} + +func startAndMeasureTraffic(t *testing.T, ate *ondatra.ATEDevice, duration time.Duration) (float64, float64) { + t.Helper() + ate.OTG().StartTraffic(t) + time.Sleep(duration) + // Measure only while traffic is running + gnmi.OTG().Port(ate.Port(t, "port1").ID()).Counters() + txRate := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port1").ID()).OutRate().State()) + rxRate := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(ate.Port(t, "port1").ID()).InRate().State()) + // Stop traffic *after* measurement + ate.OTG().StopTraffic(t) + // TODO: currently not implemented to get txRateInKbps from traffic counters. + txRateKbps := (txRate * 512) / 1000 // assuming 64-byte avg packet (512 bits) + rxRateKbps := (rxRate * 512) / 1000 + + t.Logf("Measured TX Rate: %.2f Kbps, RX Rate: %.2f Kbps", txRateKbps, rxRateKbps) + return float64(txRateKbps), float64(rxRateKbps) +} + +func validateRates(t *testing.T, txRateKbps, rxRateKbps float64, expectedRate float64, tolerance float64, description string) { + t.Helper() + diff := math.Abs(rxRateKbps - expectedRate) + if diff <= tolerance { + t.Logf("%s Passed: TX=%.2f Kbps, RX=%.2f Kbps (Δ=%.2f Kbps within tolerance %.2f)", description, expectedRate, rxRateKbps, diff, tolerance) + } else { + t.Errorf("%s Failed: TX=%.2f Kbps, RX=%.2f Kbps (Δ=%.2f Kbps exceeds tolerance %.2f)", description, expectedRate, rxRateKbps, diff, tolerance) + } +} + +func verifyTrafficAndLog(t *testing.T, label string, ok bool) { + t.Helper() + if ok { + t.Logf("%s: Verification passed", label) + } else { + t.Errorf("%s: Verification failed", label) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := gnmi.OC() + // Configure interfaces + p1 := dut.Port(t, "port1").Name() + i1 := dutPort1.NewOCInterface(p1, dut) + gnmi.Replace(t, dut, d.Interface(p1).Config(), i1) + + p2 := dut.Port(t, "port2").Name() + i2 := dutPort2.NewOCInterface(p2, dut) + gnmi.Replace(t, dut, d.Interface(p2).Config(), i2) + fptest.ConfigureDefaultNetworkInstance(t, dut) +} + +func configureDUTTrafficPolicy(t *testing.T, dut *ondatra.DUTDevice, portName string) { + t.Helper() + if deviations.TunnelConfigPathUnsupported(dut) { + gnmiClient := dut.RawAPIs().GNMI(t) + cliCommands := fmt.Sprintf(` + policy-map type quality-of-service ARP-policing-Qos + class ARP-CM + set traffic-class 2 + police rate %d kbps burst-size %d kbytes + class-map type qos match-any ARP-CM + match mac access-group ARP-policing-Macl + mac access-list ARP-policing-Macl + counters per-entry + 10 permit any any arp payload offset 1 pattern 0x00000001 mask 0xffffff00 + interface %s + service-policy type qos input ARP-policing-Qos + `, cir, burstCount, portName) + gpbSetRequest := buildCLIConfigRequest(cliCommands) + + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("gnmiClient.Set() with unexpected error: %v", err) + } + } else { + dp := dut.Port(t, "port1").Name() + config := &oc.Root{} + qos := config.GetOrCreateQos() + // Classifier for ARP + classifier := qos.GetOrCreateClassifier(classifierName) + classifier.SetName(classifierName) + classifier.SetType(oc.Qos_Classifier_Type_ETHERNET) + term, err := classifier.NewTerm(newTermName) + if err != nil { + t.Fatalf("Failed to create classifier term: %v", err) + } + term.SetId(newTermName) + l2 := term.GetOrCreateConditions().GetOrCreateL2() + l2.SetEthertype(oc.UnionUint16(ethType)) + term.GetOrCreateActions().SetTargetGroup(groupName) + // Scheduler Policy with One-Rate Two-Color + schedPolicy := qos.GetOrCreateSchedulerPolicy(SchedulerName) + schedPolicy.SetName(SchedulerName) + + scheduler := schedPolicy.GetOrCreateScheduler(0) + scheduler.SetSequence(0) + scheduler.SetPriority(oc.Scheduler_Priority_UNSET) + + input := scheduler.GetOrCreateInput(groupName) + input.SetId(groupName) + + or2c := scheduler.GetOrCreateOneRateTwoColor() + or2c.SetCir(cir) + or2c.SetBc(burstCount) + or2c.GetOrCreateExceedAction().SetDrop(true) + + // Apply to interface + qosIntf := qos.GetOrCreateInterface(dp) + qosIntf.SetInterfaceId(dp) + if deviations.InterfaceRefConfigUnsupported(dut) { + qosIntf.GetOrCreateInterfaceRef().SetInterface(dp) + } + // Push full config + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), qos) + } +} + +// configureATE configures port1 and port2 on the ATE. +func configureATE(t *testing.T, ate *ondatra.ATEDevice) gosnappi.Config { + t.Helper() + config := gosnappi.NewConfig() + + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + + otgPort1.AddToOTG(config, ap1, &dutPort1) + otgPort2.AddToOTG(config, ap2, &dutPort2) + return config +} + +func addFlow(t *testing.T, config gosnappi.Config) { + t.Helper() + config.Flows().Clear() + flow := config.Flows().Add() + flow.SetName(flowName) + flow.Size().SetFixed(fixedSize) + flow.Rate().SetKbps(rateKbps) + flow.TxRx().Port().SetTxName("port1").SetRxNames([]string{"port1"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(otgPort1.MAC) + ethHeader.Dst().SetValue(macB) + arpHeader := flow.Packet().Add().Arp() + arpHeader.SenderHardwareAddr().SetValue(otgPort1.MAC) + arpHeader.SenderProtocolAddr().SetValue(otgPort1.IPv4) + arpHeader.TargetHardwareAddr().SetValue("00:00:00:00:00:00") + arpHeader.TargetProtocolAddr().SetValue(dutPort1.IPv4) +} + +func verifyPortsUp(t *testing.T, dev *ondatra.Device) { + t.Helper() + t.Log("Verifying port status") + for _, p := range dev.Ports() { + status := gnmi.Get(t, dev, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if status != oc.Interface_OperStatus_UP { + t.Fatalf("[%s]: Interface %s status: got %v, expected UP", "verifyPortsUp", p.Name(), status) + } + } +} + +func verifyPortTraffic(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) bool { + otgutils.LogPortMetrics(t, ate.OTG(), config) + return validatePortTraffic(t, ate, ate.Port(t, "port1").ID(), false, "flow-unrestricted") +} + +func verifyPortTrafficwithCir(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config) bool { + otgutils.LogPortMetrics(t, ate.OTG(), config) + return validatePortTraffic(t, ate, ate.Port(t, "port1").ID(), true, "flow-CIR-restricted") +} + +func validatePortTraffic(t *testing.T, ate *ondatra.ATEDevice, portID string, expectDrop bool, flowName string) bool { + t.Helper() + const fn = "validatePortTraffic" + + countersPath := gnmi.OTG().Port(portID).Counters() + txRate := gnmi.Get(t, ate.OTG(), countersPath.OutFrames().State()) + rxRate := gnmi.Get(t, ate.OTG(), countersPath.InFrames().State()) + + isWithinTolerance := func(expected, actual uint64) bool { + return actual >= expected-tolerance && actual <= expected+tolerance + } + + // TX validation + txVal, ok := gnmi.Watch(t, ate.OTG(), countersPath.OutFrames().State(), timeout, + func(val *ygnmi.Value[uint64]) bool { + v, present := val.Val() + return val.IsPresent() && present && isWithinTolerance(txRate, v) + }).Await(t) + + if !ok { + t.Errorf("[%s] TX did not reach expected count (%d)", fn, txRate) + return false + } + + // RX validation + rxVal, ok := gnmi.Watch(t, ate.OTG(), countersPath.InFrames().State(), timeout, + func(val *ygnmi.Value[uint64]) bool { + v, present := val.Val() + return val.IsPresent() && present && isWithinTolerance(rxRate, v) + }).Await(t) + + if !ok { + if expectDrop { + t.Logf("[%s] Expected packet drop verified (RX lower than TX)", fn) + } else { + t.Errorf("[%s] RX packets did not match expected TX count (%d)", fn, txRate) + return false + } + } + + // Octets validation (only for CIR test) + if expectDrop { + rxOctets := gnmi.Get(t, ate.OTG(), countersPath.InOctets().State()) + txOctets := gnmi.Get(t, ate.OTG(), countersPath.OutOctets().State()) + if txOctets > rxOctets && rxOctets != 0 && txOctets != 0 { + t.Logf("[%s] In/Out Octets: TX=%d, RX=%d", fn, txOctets, rxOctets) + } else { + t.Errorf("[%s] Failed to validate In/Out Octets", fn) + return false + } + } + + // Final log + txPkts, _ := txVal.Val() + rxPkts, _ := rxVal.Val() + t.Logf("[%s] %s: TX=%d, RX=%d", fn, flowName, txPkts, rxPkts) + + return true +} + +// Support method to execute GNMIC commands +func buildCLIConfigRequest(config string) *gpb.SetRequest { + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Origin: "cli", + Elem: []*gpb.PathElem{}, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_AsciiVal{ + AsciiVal: config, + }, + }, + }, + }, + } + return gpbSetRequest +} diff --git a/feature/qos/otg_tests/Ingress_ARP_policing/metadata.textproto b/feature/qos/otg_tests/Ingress_ARP_policing/metadata.textproto new file mode 100644 index 00000000000..cb333910426 --- /dev/null +++ b/feature/qos/otg_tests/Ingress_ARP_policing/metadata.textproto @@ -0,0 +1,18 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "8f9b64a4-a631-45c2-be53-da004f5b3e41" +plan_id: "CPT-1.1" +description: "Interface based ARP policer" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + tunnel_config_path_unsupported: true + interface_ref_interface_id_format: true + } +} \ No newline at end of file diff --git a/feature/qos/otg_tests/egress_strict_priority_scheduler_test/README.md b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/README.md index be2d7963ed2..33e004f40dd 100644 --- a/feature/qos/otg_tests/egress_strict_priority_scheduler_test/README.md +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/README.md @@ -167,6 +167,512 @@ This test validates the proper functionality of an egress strict priority schedu * Loss Rate: Capture packet loss for every generated flow and verify that loss for each flow does not exceed expected loss specified in the tables above. * Telemetry: Utilize OpenConfig telemetry parameters to validate that per queue dropped packets statistics corresponds (with error margin) to the packet loss reported for every flow matching that particular queue. +## Canonical OC +```json +{ + "qos": { + "openconfig-qos:classifiers": { + "classifier": [ + { + "config": { + "name": "dscp_based_classifier_ipv4", + "type": "IPV4" + }, + "name": "dscp_based_classifier_ipv4", + "state": { + "name": "dscp_based_classifier_ipv4", + "type": "IPV4" + }, + "terms": { + "term": [ + { + "actions": { + "config": { + "target-group": "target-group-BE1" + }, + "state": { + "target-group": "target-group-BE1" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [0] + }, + "state": { + "dscp": 0 + } + } + }, + "config": { + "id": "0" + }, + "id": "0", + "state": { + "id": "0" + } + }, + { + "actions": { + "config": { + "target-group": "target-group-AF1" + }, + "state": { + "target-group": "target-group-AF1" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [1] + }, + "state": { + "dscp": 1 + } + } + }, + "config": { + "id": "1" + }, + "id": "1", + "state": { + "id": "1" + } + }, + { + "actions": { + "config": { + "target-group": "target-group-AF2" + }, + "state": { + "target-group": "target-group-AF2" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [2] + }, + "state": { + "dscp": 2 + } + } + }, + "config": { + "id": "2" + }, + "id": "2", + "state": { + "id": "2" + } + }, + { + "actions": { + "config": { + "target-group": "target-group-AF3" + }, + "state": { + "target-group": "target-group-AF3" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [3] + }, + "state": { + "dscp": 3 + } + } + }, + "config": { + "id": "3" + }, + "id": "3", + "state": { + "id": "3" + } + }, + { + "actions": { + "config": { + "target-group": "target-group-AF4" + }, + "state": { + "target-group": "target-group-AF4" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [4, 5] + }, + "state": { + "dscp-set": [4, 5] + } + } + }, + "config": { + "id": "4" + }, + "id": "4", + "state": { + "id": "4" + } + }, + { + "actions": { + "config": { + "target-group": "target-group-NC1" + }, + "state": { + "target-group": "target-group-NC1" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [6, 7] + }, + "state": { + "dscp-set": [6, 7] + } + } + }, + "config": { + "id": "5" + }, + "id": "5", + "state": { + "id": "5" + } + } + ] + } + } + ] + }, + "openconfig-qos:forwarding-groups": { + "forwarding-group": [ + { + "config": { + "name": "target-group-AF1", + "output-queue": "AF1" + }, + "name": "target-group-AF1" + }, + { + "config": { + "name": "target-group-AF2", + "output-queue": "AF2" + }, + "name": "target-group-AF2" + }, + { + "config": { + "name": "target-group-AF3", + "output-queue": "AF3" + }, + "name": "target-group-AF3" + }, + { + "config": { + "name": "target-group-AF4", + "output-queue": "AF4" + }, + "name": "target-group-AF4" + }, + { + "config": { + "name": "target-group-BE1", + "output-queue": "BE1" + }, + "name": "target-group-BE1" + }, + { + "config": { + "name": "target-group-NC1", + "output-queue": "NC1" + }, + "name": "target-group-NC1" + } + ] + }, + "openconfig-qos:interfaces": { + "interface": [ + { + "config": { + "interface-id": "Ethernet3/1" + }, + "interface-id": "Ethernet3/1" + }, + { + "config": { + "interface-id": "Ethernet3/1.1" + }, + "interface-id": "Ethernet3/1.1" + }, + { + "config": { + "interface-id": "Ethernet3/1.2" + }, + "interface-id": "Ethernet3/1.2" + }, + { + "config": { + "interface-id": "Ethernet3/1.3" + }, + "interface-id": "Ethernet3/1.3" + }, + { + "config": { + "interface-id": "Ethernet3/1.4" + }, + "interface-id": "Ethernet3/1.4" + } + ] + }, + "openconfig-qos:queues": { + "queue": [ + { + "config": { + "name": "AF1" + }, + "name": "AF1" + }, + { + "config": { + "name": "AF2" + }, + "name": "AF2" + }, + { + "config": { + "name": "AF3" + }, + "name": "AF3" + }, + { + "config": { + "name": "AF4" + }, + "name": "AF4" + }, + { + "config": { + "name": "BE1" + }, + "name": "BE1" + }, + { + "config": { + "name": "NC1" + }, + "name": "NC1" + } + ] + }, + "openconfig-qos:scheduler-policies": { + "scheduler-policy": [ + { + "config": { + "name": "scheduler" + }, + "name": "scheduler", + "schedulers": { + "scheduler": [ + { + "config": { + "priority": "STRICT", + "sequence": 5, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "AF1", + "input-type": "QUEUE", + "queue": "AF1" + }, + "id": "AF1", + "state": { + "id": "AF1", + "input-type": "QUEUE", + "queue": "AF1" + } + } + ] + }, + "sequence": 5, + "state": { + "priority": "STRICT", + "sequence": 5, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + } + }, + { + "config": { + "priority": "STRICT", + "sequence": 4, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "AF2", + "input-type": "QUEUE", + "queue": "AF2" + }, + "id": "AF2", + "state": { + "id": "AF2", + "input-type": "QUEUE", + "queue": "AF2" + } + } + ] + }, + "sequence": 4, + "state": { + "priority": "STRICT", + "sequence": 4, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + } + }, + { + "config": { + "priority": "STRICT", + "sequence": 3, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "AF3", + "input-type": "QUEUE", + "queue": "AF3" + }, + "id": "AF3", + "state": { + "id": "AF3", + "input-type": "QUEUE", + "queue": "AF3" + } + } + ] + }, + "sequence": 3, + "state": { + "priority": "STRICT", + "sequence": 3, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + } + }, + { + "config": { + "priority": "STRICT", + "sequence": 2, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "AF4", + "input-type": "QUEUE", + "queue": "AF4" + }, + "id": "AF4", + "state": { + "id": "AF4", + "input-type": "QUEUE", + "queue": "AF4" + } + } + ] + }, + "sequence": 2, + "state": { + "priority": "STRICT", + "sequence": 2, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + } + }, + { + "config": { + "priority": "STRICT", + "sequence": 6, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "BE1", + "input-type": "QUEUE", + "queue": "BE1" + }, + "id": "BE1", + "state": { + "id": "BE1", + "input-type": "QUEUE", + "queue": "BE1" + } + } + ] + }, + "sequence": 6, + "state": { + "priority": "STRICT", + "sequence": 6, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + } + }, + { + "config": { + "priority": "STRICT", + "sequence": 1, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + }, + "inputs": { + "input": [ + { + "config": { + "id": "NC1", + "input-type": "QUEUE", + "queue": "NC1" + }, + "id": "NC1", + "state": { + "id": "NC1", + "input-type": "QUEUE", + "queue": "NC1" + } + } + ] + }, + "sequence": 1, + "state": { + "priority": "STRICT", + "sequence": 1, + "type": "openconfig-qos-types:ONE_RATE_TWO_COLOR" + } + } + ] + }, + "state": { + "name": "scheduler" + } + } + ] + } + } +} +``` + ## OpenConfig Path and RPC Coverage The below yaml defines the OC paths intended to be covered by this test. diff --git a/feature/qos/otg_tests/egress_strict_priority_scheduler_test/egress_strict_priority_scheduler_test.go b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/egress_strict_priority_scheduler_test.go new file mode 100644 index 00000000000..076e32b331b --- /dev/null +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/egress_strict_priority_scheduler_test.go @@ -0,0 +1,2138 @@ +// Copyright 2025 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 egress_strict_priority_scheduler_test + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/qoscfg" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +var ( + dutIngressPort1AteP1 = attrs.Attributes{IPv4: "198.51.100.1", IPv4Len: 30, IPv6: "2001:db8::1", IPv6Len: 126} + dutIngressPort2AteP2 = attrs.Attributes{IPv4: "198.51.100.5", IPv4Len: 30, IPv6: "2001:db8::5", IPv6Len: 126} + dutEgressPort3AteP3 = attrs.Attributes{IPv4: "198.51.100.9", IPv4Len: 30, IPv6: "2001:db8::9", IPv6Len: 126} + + ateTxP1 = attrs.Attributes{Name: "ate1", MAC: "00:01:01:01:01:01", IPv4: "198.51.100.2", IPv4Len: 30, IPv6: "2001:db8::2", IPv6Len: 126} + ateTxP2 = attrs.Attributes{Name: "ate2", MAC: "00:01:01:01:01:02", IPv4: "198.51.100.6", IPv4Len: 30, IPv6: "2001:db8::6", IPv6Len: 126} + ateRxP3 = attrs.Attributes{Name: "ate3", MAC: "00:01:01:01:01:03", IPv4: "198.51.100.10", IPv4Len: 30, IPv6: "2001:db8::a", IPv6Len: 126} + + mplsLabel uint32 = 1001 + tolerance float32 = 5.0 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestEgressStrictPrioritySchedulerTraffic(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + t.Logf("Configuring Hardware Init") + configureHardwareInit(t, dut) + + t.Logf("Configuring QoS Global parameters") + cfgplugins.NewQosInitialize(t, dut) + + verifyEgressStrictPrioritySchedulerTrafficIPv4(t, dut) + verifyEgressStrictPrioritySchedulerTrafficIPv6(t, dut) + verifyEgressStrictPrioritySchedulerTrafficMPLS(t, dut) +} + +func verifyEgressStrictPrioritySchedulerTrafficIPv4(t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + // Configure DUT interfaces and QoS. + ConfigureDUTIntfIPv4(t, dut) + ConfigureDUTQoSIPv4(t, dut) + + // Configure ATE interfaces. + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + top := gosnappi.NewConfig() + + ateTxP1.AddToOTG(top, ap1, &dutIngressPort1AteP1) + ateTxP2.AddToOTG(top, ap2, &dutIngressPort2AteP2) + ateRxP3.AddToOTG(top, ap3, &dutEgressPort3AteP3) + ate.OTG().PushConfig(t, top) + + createTrafficFlowsIPv4(t, ate, top, dut) + t.Run("\n*** Running test for IPv4 ***\n", func(t *testing.T) { + + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + queues := netutil.CommonTrafficQueues(t, dut) + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + } + + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-nc1": { + frameSize: 512, + trafficRate: 1, + dscp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af4": { + frameSize: 512, + trafficRate: 30, + dscp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af3": { + frameSize: 512, + trafficRate: 12, + dscp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 10, + expectedThroughputPct: 50.0, + dscp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af2": { + frameSize: 512, + trafficRate: 10, + dscp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af1": { + frameSize: 512, + trafficRate: 12, + dscp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + }, + } + + if deviations.InterfaceOutputQueueNonStandardName(dut) { + + // Configuring the non-standard queue names. + for flowName, data := range trafficFlows { + if strings.Contains(strings.ToUpper(flowName), "BE1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(0) + } + if strings.Contains(strings.ToUpper(flowName), "AF1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(1) + } + if strings.Contains(strings.ToUpper(flowName), "AF2") { + data.queue = dp3.Name() + "-" + strconv.Itoa(2) + } + if strings.Contains(strings.ToUpper(flowName), "AF3") { + data.queue = dp3.Name() + "-" + strconv.Itoa(3) + } + if strings.Contains(strings.ToUpper(flowName), "AF4") { + data.queue = dp3.Name() + "-" + strconv.Itoa(4) + } + if strings.Contains(strings.ToUpper(flowName), "NC1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(5) + } + } + + } + + ateOutPkts := make(map[string]uint64) + ateInPkts := make(map[string]uint64) + dutQosPktsBeforeTraffic := make(map[string]uint64) + dutQosPktsAfterTraffic := make(map[string]uint64) + dutQosDroppedPktsBeforeTraffic := make(map[string]uint64) + dutQosDroppedPktsAfterTraffic := make(map[string]uint64) + + // Set the initial counters to 0. + for _, data := range trafficFlows { + ateOutPkts[data.queue] = 0 + ateInPkts[data.queue] = 0 + dutQosPktsBeforeTraffic[data.queue] = 0 + dutQosPktsAfterTraffic[data.queue] = 0 + dutQosDroppedPktsBeforeTraffic[data.queue] = 0 + dutQosDroppedPktsAfterTraffic[data.queue] = 0 + } + + uniqueQueues := make(map[string]bool) + for _, data := range trafficFlows { + uniqueQueues[data.queue] = true + } + + // Get QoS egress packet counters before the traffic. + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + for queue := range uniqueQueues { + count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("TransmitPkts count for queue %s on interface %q not available within %v", queue, dp3.Name(), timeout) + } + dutQosPktsBeforeTraffic[queue], _ = count.Val() + + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedPkts count for queue %s on interface %q not available within %v", dp3.Name(), queue, timeout) + } + dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val() + } + + t.Logf("Before TX values map: %v\n", dutQosPktsBeforeTraffic) + t.Logf("Before DROP values map: %v\n", dutQosDroppedPktsBeforeTraffic) + + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp1.Name(), dp3.Name()) + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp2.Name(), dp3.Name()) + t.Logf("Sending traffic flows:\n") + + ate.OTG().StartTraffic(t) + + time.Sleep(15 * time.Second) + + ate.OTG().StopTraffic(t) + + for flowName := range trafficFlows { + waitForTraffic(t, ate.OTG(), flowName, 10) + } + + t.Logf("Printing aggregated flow metrics from OTG: \n") + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for trafficID, data := range trafficFlows { + + t.Logf("Retrieving statistics for %s\n", trafficID) + ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) + ateRxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().InPkts().State()) + if ateTxPkts == 0 { + t.Fatalf("Flow %s did not send any packets, please check tester configuration.\n", trafficID) + } + + lossPct := (float32)((float64(ateTxPkts-ateRxPkts) * 100.0) / float64(ateTxPkts)) + + t.Logf("Flow: %s\t\t| EgressQueue:%s\t\t| Loss%%: %.2f%%\t\t| Rx%%:%.2f%%\t\t| Tolerance%%: %.2f%%", trafficID, data.queue, lossPct, 100-lossPct, tolerance) + + if got, want := 100.0-lossPct, data.expectedThroughputPct; got < want-tolerance || got > want+tolerance { + t.Errorf("Expected throughput for queue %q should be within [%.2f%%, %.2f%%]: got %.2f%%\n", data.queue, want-tolerance, want+tolerance, got) + } + + ateOutPkts[data.queue] += ateTxPkts + ateInPkts[data.queue] += ateRxPkts + + } + + header := "| %-20s | %-20s | %-18s | %-18s | %-24s | %-24s |" + row := "| %-20s | %-20s | %-18d | %-18d | %-24d | %-24d |" + t.Logf(header, "Intf", "Queue", "ATE Tx frames", "DUT Tx frames", "ATE Dropped frames", "DUT Dropped frames") + + for queue := range uniqueQueues { + dutQueueTransmitPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State()) + dutQueueDroppedPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State()) + + dutQueueTransmitPkts := dutQueueTransmitPktsTotal - dutQosPktsBeforeTraffic[queue] + dutQueueDroppedPkts := dutQueueDroppedPktsTotal - dutQosDroppedPktsBeforeTraffic[queue] + + ateQueueTransmitPkts := ateOutPkts[queue] + var ateQueueDroppedPkts uint64 = 0 + if ateOutPkts[queue] >= ateInPkts[queue] { + ateQueueDroppedPkts = ateOutPkts[queue] - ateInPkts[queue] + } else { + t.Fatalf("ATE reports more received pkts than sent pkts on interface %s and egress queue %s", dp3.Name(), queue) + } + t.Logf(row, dp3.Name(), queue, ateQueueTransmitPkts, dutQueueTransmitPkts, ateQueueDroppedPkts, dutQueueDroppedPkts) + } + + }) + +} + +func verifyEgressStrictPrioritySchedulerTrafficIPv6(t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + // Configure DUT interfaces and QoS. + ConfigureDUTIntfIPv6(t, dut) + ConfigureDUTQoSIPv6(t, dut) + + // Configure ATE interfaces. + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + top := gosnappi.NewConfig() + + ateTxP1.AddToOTG(top, ap1, &dutIngressPort1AteP1) + ateTxP2.AddToOTG(top, ap2, &dutIngressPort2AteP2) + ateRxP3.AddToOTG(top, ap3, &dutEgressPort3AteP3) + ate.OTG().PushConfig(t, top) + + createTrafficFlowsIPv6(t, ate, top, dut) + t.Run("\n*** Running test for IPv6 ***\n", func(t *testing.T) { + + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + queues := netutil.CommonTrafficQueues(t, dut) + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp []uint32 + queue string + inputIntf attrs.Attributes + } + + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: []uint32{48, 49, 50, 51, 52, 53, 54, 55}, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-nc1": { + frameSize: 512, + trafficRate: 1, + dscp: []uint32{56, 57, 58, 59, 60, 61, 62, 63}, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: []uint32{32, 33, 34, 35, 36, 37, 38, 39}, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af4": { + frameSize: 512, + trafficRate: 30, + dscp: []uint32{40, 41, 42, 43, 44, 45, 46, 47}, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: []uint32{24, 25, 26, 27}, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af3": { + frameSize: 512, + trafficRate: 12, + dscp: []uint32{28, 29, 30, 31}, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 10, + expectedThroughputPct: 50.0, + dscp: []uint32{16, 17, 18, 19}, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af2": { + frameSize: 512, + trafficRate: 10, + dscp: []uint32{20, 21, 22, 23}, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{8, 9, 10, 11}, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af1": { + frameSize: 512, + trafficRate: 12, + dscp: []uint32{12, 13, 14, 15}, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{0, 1, 2, 3}, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{4, 5, 6, 7}, + queue: queues.BE1, + inputIntf: ateTxP2, + }, + } + + if deviations.InterfaceOutputQueueNonStandardName(dut) { + + // Configuring the non-standard queue names. + for flowName, data := range trafficFlows { + if strings.Contains(strings.ToUpper(flowName), "BE1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(0) + } + if strings.Contains(strings.ToUpper(flowName), "AF1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(1) + } + if strings.Contains(strings.ToUpper(flowName), "AF2") { + data.queue = dp3.Name() + "-" + strconv.Itoa(2) + } + if strings.Contains(strings.ToUpper(flowName), "AF3") { + data.queue = dp3.Name() + "-" + strconv.Itoa(3) + } + if strings.Contains(strings.ToUpper(flowName), "AF4") { + data.queue = dp3.Name() + "-" + strconv.Itoa(4) + } + if strings.Contains(strings.ToUpper(flowName), "NC1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(5) + } + } + } + + ateOutPkts := make(map[string]uint64) + ateInPkts := make(map[string]uint64) + dutQosPktsBeforeTraffic := make(map[string]uint64) + dutQosPktsAfterTraffic := make(map[string]uint64) + dutQosDroppedPktsBeforeTraffic := make(map[string]uint64) + dutQosDroppedPktsAfterTraffic := make(map[string]uint64) + + // Set the initial counters to 0. + for _, data := range trafficFlows { + ateOutPkts[data.queue] = 0 + ateInPkts[data.queue] = 0 + dutQosPktsBeforeTraffic[data.queue] = 0 + dutQosPktsAfterTraffic[data.queue] = 0 + dutQosDroppedPktsBeforeTraffic[data.queue] = 0 + dutQosDroppedPktsAfterTraffic[data.queue] = 0 + } + + uniqueQueues := make(map[string]bool) + for _, data := range trafficFlows { + uniqueQueues[data.queue] = true + } + // Get QoS egress packet counters before the traffic. + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + for queue := range uniqueQueues { + count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("TransmitPkts count for queue %s on interface %q not available within %v", queue, dp3.Name(), timeout) + } + dutQosPktsBeforeTraffic[queue], _ = count.Val() + + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedPkts count for queue %s on interface %q not available within %v", dp3.Name(), queue, timeout) + } + dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val() + } + + t.Logf("Before TX values map: %v\n", dutQosPktsBeforeTraffic) + t.Logf("Before DROP values map: %v\n", dutQosDroppedPktsBeforeTraffic) + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp1.Name(), dp3.Name()) + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp2.Name(), dp3.Name()) + t.Logf("Sending traffic flows:\n") + ate.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + ate.OTG().StopTraffic(t) + + time.Sleep(5 * time.Second) + + t.Logf("Printing aggregated flow metrics from OTG: \n") + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for trafficID, data := range trafficFlows { + + t.Logf("Retrieving statistics for %s\n", trafficID) + ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) + ateRxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().InPkts().State()) + if ateTxPkts == 0 { + t.Fatalf("Flow %s did not send any packets, please check tester configuration.\n", trafficID) + } + + lossPct := (float32)((float64(ateTxPkts-ateRxPkts) * 100.0) / float64(ateTxPkts)) + + t.Logf("Flow: %s\t\t|EgressQueue:%s\t\t|Loss%%: %.2f%%\t\t|ExpectedRx%%:%.2f%%\t\t|Tolerance%%: %.2f%%", trafficID, data.queue, lossPct, 100-lossPct, tolerance) + + if got, want := 100.0-lossPct, data.expectedThroughputPct; got < want-tolerance || got > want+tolerance { + t.Errorf("Expected throughput for queue %q should be within [%.2f%%, %.2f%%]: got %.2f%%\n", data.queue, want-tolerance, want+tolerance, got) + } + + ateOutPkts[data.queue] += ateTxPkts + ateInPkts[data.queue] += ateRxPkts + + } + + header := "| %-20s | %-20s | %-18s | %-18s | %-24s | %-24s |" + row := "| %-20s | %-20s | %-18d | %-18d | %-24d | %-24d |" + t.Logf(header, "Intf", "Queue", "ATE Tx frames", "DUT Tx frames", "ATE Dropped frames", "DUT Dropped frames") + + for queue := range uniqueQueues { + dutQueueTransmitPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State()) + dutQueueDroppedPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State()) + + dutQueueTransmitPkts := dutQueueTransmitPktsTotal - dutQosPktsBeforeTraffic[queue] + dutQueueDroppedPkts := dutQueueDroppedPktsTotal - dutQosDroppedPktsBeforeTraffic[queue] + + ateQueueTransmitPkts := ateOutPkts[queue] + var ateQueueDroppedPkts uint64 = 0 + if ateOutPkts[queue] >= ateInPkts[queue] { + ateQueueDroppedPkts = ateOutPkts[queue] - ateInPkts[queue] + } else { + t.Fatalf("ATE reports more received pkts than sent pkts on interface %s and egress queue %s", dp3.Name(), queue) + } + t.Logf(row, dp3.Name(), queue, ateQueueTransmitPkts, dutQueueTransmitPkts, ateQueueDroppedPkts, dutQueueDroppedPkts) + } + + }) + +} + +func verifyEgressStrictPrioritySchedulerTrafficMPLS(t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + // Configure DUT interfaces and QoS. + ConfigureDUTIntfIPv4(t, dut) + ConfigureDUTQoSMPLS(t, dut) + + // Configure ATE interfaces. + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + top := gosnappi.NewConfig() + + ateTxP1.AddToOTG(top, ap1, &dutIngressPort1AteP1) + ateTxP2.AddToOTG(top, ap2, &dutIngressPort2AteP2) + ateRxP3.AddToOTG(top, ap3, &dutEgressPort3AteP3) + ate.OTG().PushConfig(t, top) + + t.Run("\n*** Running test for MPLS ***\n", func(t *testing.T) { + + ate.OTG().StartProtocols(t) + + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + createTrafficFlowsMPLS(t, ate, top, dut) + + queues := netutil.CommonTrafficQueues(t, dut) + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + } + + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-nc1": { + frameSize: 512, + trafficRate: 1, + dscp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af4": { + frameSize: 512, + trafficRate: 30, + dscp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af3": { + frameSize: 512, + trafficRate: 12, + dscp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 10, + expectedThroughputPct: 50.0, + dscp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af2": { + frameSize: 512, + trafficRate: 10, + dscp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af1": { + frameSize: 512, + trafficRate: 12, + dscp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + }, + } + + if deviations.InterfaceOutputQueueNonStandardName(dut) { + + // Configuring the non-standard queue names. + for flowName, data := range trafficFlows { + if strings.Contains(strings.ToUpper(flowName), "BE1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(0) + } + if strings.Contains(strings.ToUpper(flowName), "AF1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(1) + } + if strings.Contains(strings.ToUpper(flowName), "AF2") { + data.queue = dp3.Name() + "-" + strconv.Itoa(2) + } + if strings.Contains(strings.ToUpper(flowName), "AF3") { + data.queue = dp3.Name() + "-" + strconv.Itoa(3) + } + if strings.Contains(strings.ToUpper(flowName), "AF4") { + data.queue = dp3.Name() + "-" + strconv.Itoa(4) + } + if strings.Contains(strings.ToUpper(flowName), "NC1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(5) + } + } + + } + ateOutPkts := make(map[string]uint64) + ateInPkts := make(map[string]uint64) + dutQosPktsBeforeTraffic := make(map[string]uint64) + dutQosPktsAfterTraffic := make(map[string]uint64) + dutQosDroppedPktsBeforeTraffic := make(map[string]uint64) + dutQosDroppedPktsAfterTraffic := make(map[string]uint64) + + // Set the initial counters to 0. + for _, data := range trafficFlows { + ateOutPkts[data.queue] = 0 + ateInPkts[data.queue] = 0 + dutQosPktsBeforeTraffic[data.queue] = 0 + dutQosPktsAfterTraffic[data.queue] = 0 + dutQosDroppedPktsBeforeTraffic[data.queue] = 0 + dutQosDroppedPktsAfterTraffic[data.queue] = 0 + } + + uniqueQueues := make(map[string]bool) + for _, data := range trafficFlows { + uniqueQueues[data.queue] = true + } + + // Get QoS egress packet counters before the traffic. + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + for queue := range uniqueQueues { + count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("TransmitPkts count for queue %s on interface %q not available within %v", queue, dp3.Name(), timeout) + } + dutQosPktsBeforeTraffic[queue], _ = count.Val() + + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedPkts count for queue %s on interface %q not available within %v", dp3.Name(), queue, timeout) + } + dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val() + } + + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp1.Name(), dp3.Name()) + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp2.Name(), dp3.Name()) + t.Logf("Sending traffic flows:\n") + ate.OTG().StartTraffic(t) + + time.Sleep(15 * time.Second) + + ate.OTG().StopTraffic(t) + + time.Sleep(5 * time.Second) + + t.Logf("Printing aggregated flow metrics from OTG: \n") + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for trafficID, data := range trafficFlows { + + t.Logf("Retrieving statistics for %s\n", trafficID) + ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) + ateRxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().InPkts().State()) + if ateTxPkts == 0 { + t.Fatalf("Flow %s did not send any packets, please check tester configuration.\n", trafficID) + } + + lossPct := (float32)((float64(ateTxPkts-ateRxPkts) * 100.0) / float64(ateTxPkts)) + + t.Logf("Flow: %s\t\t|EgressQueue:%s\t\t|Loss%%: %.2f%%\t\t|ExpectedRx%%:%.2f%%\t\t|Tolerance%%: %.2f%%", trafficID, data.queue, lossPct, 100-lossPct, tolerance) + + if got, want := 100.0-lossPct, data.expectedThroughputPct; got < want-tolerance || got > want+tolerance { + t.Errorf("Expected throughput for queue %q should be within [%.2f%%, %.2f%%]: got %.2f%%\n", data.queue, want-tolerance, want+tolerance, got) + } + + ateOutPkts[data.queue] += ateTxPkts + ateInPkts[data.queue] += ateRxPkts + + } + + header := "| %-20s | %-20s | %-18s | %-18s | %-24s | %-24s |" + row := "| %-20s | %-20s | %-18d | %-18d | %-24d | %-24d |" + t.Logf(header, "Intf", "Queue", "ATE Tx frames", "DUT Tx frames", "ATE Dropped frames", "DUT Dropped frames") + + for queue := range uniqueQueues { + dutQueueTransmitPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State()) + dutQueueDroppedPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State()) + + dutQueueTransmitPkts := dutQueueTransmitPktsTotal - dutQosPktsBeforeTraffic[queue] + dutQueueDroppedPkts := dutQueueDroppedPktsTotal - dutQosDroppedPktsBeforeTraffic[queue] + + ateQueueTransmitPkts := ateOutPkts[queue] + var ateQueueDroppedPkts uint64 = 0 + if ateOutPkts[queue] >= ateInPkts[queue] { + ateQueueDroppedPkts = ateOutPkts[queue] - ateInPkts[queue] + } else { + t.Fatalf("ATE reports more received pkts than sent pkts on interface %s and egress queue %s", dp3.Name(), queue) + } + t.Logf(row, dp3.Name(), queue, ateQueueTransmitPkts, dutQueueTransmitPkts, ateQueueDroppedPkts, dutQueueDroppedPkts) + } + + }) + +} + +func createTrafficFlowsIPv4(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, dut *ondatra.DUTDevice) { + t.Helper() + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + } + + queues := netutil.CommonTrafficQueues(t, dut) + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-nc1": { + frameSize: 512, + trafficRate: 1, + dscp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af4": { + frameSize: 512, + trafficRate: 30, + dscp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af3": { + frameSize: 512, + trafficRate: 12, + dscp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 10, + expectedThroughputPct: 50.0, + dscp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af2": { + frameSize: 512, + trafficRate: 10, + dscp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af1": { + frameSize: 512, + trafficRate: 12, + dscp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + }, + } + top.Flows().Clear() + + for trafficID, data := range trafficFlows { + t.Logf("Configuring flow %s\n", trafficID) + flow := top.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{data.inputIntf.Name + ".IPv4"}).SetRxNames([]string{ateRxP3.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(data.inputIntf.IPv4) + ipHeader.Dst().SetValue(ateRxP3.IPv4) + ipHeader.Priority().Dscp().Phb().SetValue(uint32(data.dscp)) + + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPercentage(float32(data.trafficRate)) + + } + ate.OTG().PushConfig(t, top) +} + +func createTrafficFlowsIPv6(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, dut *ondatra.DUTDevice) { + t.Helper() + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp []uint32 + queue string + inputIntf attrs.Attributes + } + + queues := netutil.CommonTrafficQueues(t, dut) + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: []uint32{48, 49, 50, 51, 52, 53, 54, 55}, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-nc1": { + frameSize: 512, + trafficRate: 1, + dscp: []uint32{56, 57, 58, 59, 60, 61, 62, 63}, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: []uint32{32, 33, 34, 35, 36, 37, 38, 39}, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af4": { + frameSize: 512, + trafficRate: 30, + dscp: []uint32{40, 41, 42, 43, 44, 45, 46, 47}, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: []uint32{24, 25, 26, 27}, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af3": { + frameSize: 512, + trafficRate: 12, + dscp: []uint32{28, 29, 30, 31}, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 10, + expectedThroughputPct: 50.0, + dscp: []uint32{16, 17, 18, 19}, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af2": { + frameSize: 512, + trafficRate: 10, + dscp: []uint32{20, 21, 22, 23}, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{8, 9, 10, 11}, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af1": { + frameSize: 512, + trafficRate: 12, + dscp: []uint32{12, 13, 14, 15}, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{0, 1, 2, 3}, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{4, 5, 6, 7}, + queue: queues.BE1, + inputIntf: ateTxP2, + }, + } + top.Flows().Clear() + + for trafficID, data := range trafficFlows { + t.Logf("Configuring flow %s\n", trafficID) + flow := top.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{data.inputIntf.Name + ".IPv6"}).SetRxNames([]string{ateRxP3.Name + ".IPv6"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + + ipHeader := flow.Packet().Add().Ipv6() + ipHeader.Src().SetValue(data.inputIntf.IPv6) + ipHeader.Dst().SetValue(ateRxP3.IPv6) + ipHeader.TrafficClass().SetValues([]uint32(data.dscp)) + + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPercentage(float32(data.trafficRate)) + + } + ate.OTG().PushConfig(t, top) +} + +func createTrafficFlowsMPLS(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, dut *ondatra.DUTDevice) { + t.Helper() + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + exp uint8 + queue string + inputIntf attrs.Attributes + } + + queues := netutil.CommonTrafficQueues(t, dut) + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + exp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-nc1": { + frameSize: 512, + trafficRate: 1, + exp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + exp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af4": { + frameSize: 512, + trafficRate: 30, + exp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + exp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af3": { + frameSize: 512, + trafficRate: 12, + exp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 10, + expectedThroughputPct: 50.0, + exp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af2": { + frameSize: 512, + trafficRate: 10, + exp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + exp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-af1": { + frameSize: 512, + trafficRate: 12, + exp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + exp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + exp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + }, + } + top.Flows().Clear() + + dstMac1 := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + dstMac2 := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port2").Name()).Ethernet().MacAddress().State()) + + for trafficID, data := range trafficFlows { + t.Logf("Configuring flow %s\n", trafficID) + + dstMac := "" + srcPort := "" + + if strings.Contains(strings.ToUpper(trafficID), "ATETXP1") { + dstMac = dstMac1 + srcPort = "port1" + } else if strings.Contains(strings.ToUpper(trafficID), "ATETXP2") { + dstMac = dstMac2 + srcPort = "port2" + } + + flow := top.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Port().SetTxName(srcPort).SetRxNames([]string{"port3"}) + + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + ethHeader.Dst().SetValue(dstMac) + + mplsHeader := flow.Packet().Add().Mpls() + mplsHeader.Label().SetValue(mplsLabel) + mplsHeader.TrafficClass().SetValue(uint32(data.exp)) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(data.inputIntf.IPv4) + ipHeader.Dst().SetValue(ateRxP3.IPv4) + + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPercentage(float32(data.trafficRate)) + + } + + ate.OTG().PushConfig(t, top) +} + +func ConfigureDUTIntfIPv4(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + dutIntfs := []struct { + desc string + intfName string + ipAddr string + prefixLen uint8 + }{{ + desc: "DUT input intf port1", + intfName: dp1.Name(), + ipAddr: dutIngressPort1AteP1.IPv4, + prefixLen: dutIngressPort1AteP1.IPv4Len, + }, { + desc: "DUT input intf port2", + intfName: dp2.Name(), + ipAddr: dutIngressPort2AteP2.IPv4, + prefixLen: dutIngressPort2AteP2.IPv4Len, + }, { + desc: "DUT output intf port3", + intfName: dp3.Name(), + ipAddr: dutEgressPort3AteP3.IPv4, + prefixLen: dutEgressPort3AteP3.IPv4Len, + }} + + // Configure the interfaces. + for _, intf := range dutIntfs { + t.Logf("Configure DUT interface %s with attributes %v", intf.intfName, intf) + i := &oc.Interface{ + Name: ygot.String(intf.intfName), + Description: ygot.String(intf.desc), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(true), + } + i.GetOrCreateEthernet() + s := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s.Enabled = ygot.Bool(true) + t.Logf("DUT %s %s %s requires interface enable deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + a := s.GetOrCreateAddress(intf.ipAddr) + a.PrefixLength = ygot.Uint8(intf.prefixLen) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf.intfName).Config(), i) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, intf.intfName, deviations.DefaultNetworkInstance(dut), 0) + t.Logf("DUT %s %s %s requires explicit interface in default VRF deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + } + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dp1) + fptest.SetPortSpeed(t, dp2) + fptest.SetPortSpeed(t, dp3) + t.Logf("DUT %s %s %s requires explicit port speed set deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } +} + +func ConfigureDUTIntfIPv6(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + dutIntfs := []struct { + desc string + intfName string + ipAddr string + prefixLen uint8 + }{{ + desc: "DUT input intf port1", + intfName: dp1.Name(), + ipAddr: dutIngressPort1AteP1.IPv6, + prefixLen: dutIngressPort1AteP1.IPv6Len, + }, { + desc: "DUT input intf port2", + intfName: dp2.Name(), + ipAddr: dutIngressPort2AteP2.IPv6, + prefixLen: dutIngressPort2AteP2.IPv6Len, + }, { + desc: "DUT output intf port3", + intfName: dp3.Name(), + ipAddr: dutEgressPort3AteP3.IPv6, + prefixLen: dutEgressPort3AteP3.IPv6Len, + }} + + // Configure the interfaces. + for _, intf := range dutIntfs { + t.Logf("Configure DUT interface %s with attributes %v", intf.intfName, intf) + i := &oc.Interface{ + Name: ygot.String(intf.intfName), + Description: ygot.String(intf.desc), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(true), + } + i.GetOrCreateEthernet() + s4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + s := i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + s.Enabled = ygot.Bool(true) + a := s.GetOrCreateAddress(intf.ipAddr) + a.PrefixLength = ygot.Uint8(intf.prefixLen) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf.intfName).Config(), i) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, intf.intfName, deviations.DefaultNetworkInstance(dut), 0) + t.Logf("DUT %s %s %s requires explicit interface in default VRF deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + } + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dp1) + fptest.SetPortSpeed(t, dp2) + fptest.SetPortSpeed(t, dp3) + t.Logf("DUT %s %s %s requires explicit port speed set deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } +} + +func ConfigureDUTQoSIPv4(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + cfgplugins.NewQoSQueue(t, dut, q) + + t.Logf("Create QoS forwarding groups and queue names configuration") + forwardingGroups := []cfgplugins.ForwardingGroup{ + { + Desc: "forwarding-group-BE1", + QueueName: queues.BE1, + TargetGroup: "target-group-BE1", + Priority: 0, + }, { + Desc: "forwarding-group-AF1", + QueueName: queues.AF1, + TargetGroup: "target-group-AF1", + Priority: 1, + }, { + Desc: "forwarding-group-AF2", + QueueName: queues.AF2, + TargetGroup: "target-group-AF2", + Priority: 2, + }, { + Desc: "forwarding-group-AF3", + QueueName: queues.AF3, + TargetGroup: "target-group-AF3", + Priority: 3, + }, { + Desc: "forwarding-group-AF4", + QueueName: queues.AF4, + TargetGroup: "target-group-AF4", + Priority: 4, + }, { + Desc: "forwarding-group-NC1", + QueueName: queues.NC1, + TargetGroup: "target-group-NC1", + Priority: 5, + }} + + cfgplugins.NewQoSForwardingGroup(t, dut, q, forwardingGroups) + + t.Logf("Create QoS Classifiers config") + classifiers := []cfgplugins.QosClassifier{ + { + Desc: "classifier_ipv4_be1", + Name: "dscp_based_classifier_ipv4", + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: "0", + TargetGroup: "target-group-BE1", + DscpSet: []uint8{0}, + }, { + Desc: "classifier_ipv4_af1", + Name: "dscp_based_classifier_ipv4", + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: "1", + TargetGroup: "target-group-AF1", + DscpSet: []uint8{1}, + }, { + Desc: "classifier_ipv4_af2", + Name: "dscp_based_classifier_ipv4", + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: "2", + TargetGroup: "target-group-AF2", + DscpSet: []uint8{2}, + }, { + Desc: "classifier_ipv4_af3", + Name: "dscp_based_classifier_ipv4", + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: "3", + TargetGroup: "target-group-AF3", + DscpSet: []uint8{3}, + }, { + Desc: "classifier_ipv4_af4", + Name: "dscp_based_classifier_ipv4", + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: "4", + TargetGroup: "target-group-AF4", + DscpSet: []uint8{4, 5}, + }, { + Desc: "classifier_ipv4_nc1", + Name: "dscp_based_classifier_ipv4", + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: "5", + TargetGroup: "target-group-NC1", + DscpSet: []uint8{6, 7}, + }} + + q = cfgplugins.NewQoSClassifierConfiguration(t, dut, q, classifiers) + + t.Logf("Create QoS input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type IPV4", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV4, + classifier: "dscp_based_classifier_ipv4", + }, { + desc: "Input Classifier Type IPV4", + intf: dp2.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV4, + classifier: "dscp_based_classifier_ipv4", + }} + + t.Logf("QoS input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + + t.Logf("Create QoS scheduler policies config") + schedulerPolicies := []cfgplugins.SchedulerPolicy{ + { + Desc: "scheduler-policy-BE1", + Sequence: uint32(6), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "BE1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.BE1, + TargetGroup: "target-group-BE1", + }, { + Desc: "scheduler-policy-AF1", + Sequence: uint32(5), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF1, + TargetGroup: "target-group-AF1", + }, { + Desc: "scheduler-policy-AF2", + Sequence: uint32(4), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF2", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF2, + TargetGroup: "target-group-AF2", + }, { + Desc: "scheduler-policy-AF3", + Sequence: uint32(3), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF3", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF3, + TargetGroup: "target-group-AF3", + }, { + Desc: "scheduler-policy-AF4", + Sequence: uint32(2), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF4", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF4, + TargetGroup: "target-group-AF4", + }, { + Desc: "scheduler-policy-NC1", + Sequence: uint32(1), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "NC1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.NC1, + TargetGroup: "target-group-NC1", + }} + + q = cfgplugins.NewQoSSchedulerPolicy(t, dut, q, schedulerPolicies) + + t.Logf("Create QoS output interface config") + schedulerIntfs := []cfgplugins.QoSSchedulerInterface{ + { + Desc: "output-interface-BE1", + QueueName: queues.BE1, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF1", + QueueName: queues.AF1, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF2", + QueueName: queues.AF2, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF3", + QueueName: queues.AF3, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF4", + QueueName: queues.AF4, + Scheduler: "scheduler", + }, { + Desc: "output-interface-NC1", + QueueName: queues.NC1, + Scheduler: "scheduler", + }} + + q = cfgplugins.NewQoSSchedulerInterface(t, dut, q, schedulerIntfs, "port3") + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + +} + +func ConfigureDUTQoSIPv6(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + cfgplugins.NewQoSQueue(t, dut, q) + + t.Logf("Create QoS forwarding groups and queue names configuration") + forwardingGroups := []cfgplugins.ForwardingGroup{ + { + Desc: "forwarding-group-BE1", + QueueName: queues.BE1, + TargetGroup: "target-group-BE1", + Priority: 0, + }, { + Desc: "forwarding-group-AF1", + QueueName: queues.AF1, + TargetGroup: "target-group-AF1", + Priority: 1, + }, { + Desc: "forwarding-group-AF2", + QueueName: queues.AF2, + TargetGroup: "target-group-AF2", + Priority: 2, + }, { + Desc: "forwarding-group-AF3", + QueueName: queues.AF3, + TargetGroup: "target-group-AF3", + Priority: 3, + }, { + Desc: "forwarding-group-AF4", + QueueName: queues.AF4, + TargetGroup: "target-group-AF4", + Priority: 4, + }, { + Desc: "forwarding-group-NC1", + QueueName: queues.NC1, + TargetGroup: "target-group-NC1", + Priority: 5, + }} + + cfgplugins.NewQoSForwardingGroup(t, dut, q, forwardingGroups) + + t.Logf("Create QoS Classifiers config") + classifiers := []cfgplugins.QosClassifier{ + { + Desc: "classifier_ipv6_be1", + Name: "dscp_based_classifier_ipv6", + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: "0", + TargetGroup: "target-group-BE1", + DscpSet: []uint8{0, 1, 2, 3, 4, 5, 6, 7}, + }, { + Desc: "classifier_ipv6_af1", + Name: "dscp_based_classifier_ipv6", + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: "1", + TargetGroup: "target-group-AF1", + DscpSet: []uint8{8, 9, 10, 11, 12, 13, 14, 15}, + }, { + Desc: "classifier_ipv6_af2", + Name: "dscp_based_classifier_ipv6", + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: "2", + TargetGroup: "target-group-AF2", + DscpSet: []uint8{16, 17, 18, 19, 20, 21, 22, 23}, + }, { + Desc: "classifier_ipv6_af3", + Name: "dscp_based_classifier_ipv6", + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: "3", + TargetGroup: "target-group-AF3", + DscpSet: []uint8{24, 25, 26, 27, 28, 29, 30, 31}, + }, { + Desc: "classifier_ipv6_af4", + Name: "dscp_based_classifier_ipv6", + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: "4", + TargetGroup: "target-group-AF4", + DscpSet: []uint8{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}, + }, { + Desc: "classifier_ipv6_nc1", + Name: "dscp_based_classifier_ipv6", + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: "5", + TargetGroup: "target-group-NC1", + DscpSet: []uint8{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}, + }} + + q = cfgplugins.NewQoSClassifierConfiguration(t, dut, q, classifiers) + + t.Logf("Create QoS input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type IPV6", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV6, + classifier: "dscp_based_classifier_ipv6", + }, { + desc: "Input Classifier Type IPV6", + intf: dp2.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV6, + classifier: "dscp_based_classifier_ipv6", + }} + + t.Logf("QoS input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + + t.Logf("Create QoS scheduler policies config") + schedulerPolicies := []cfgplugins.SchedulerPolicy{ + { + Desc: "scheduler-policy-BE1", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "BE1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.BE1, + TargetGroup: "target-group-BE1", + }, { + Desc: "scheduler-policy-AF1", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF1, + TargetGroup: "target-group-AF1", + }, { + Desc: "scheduler-policy-AF2", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF2", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF2, + TargetGroup: "target-group-AF2", + }, { + Desc: "scheduler-policy-AF3", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF3", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF3, + TargetGroup: "target-group-AF3", + }, { + Desc: "scheduler-policy-AF4", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF4", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF4, + TargetGroup: "target-group-AF4", + }, { + Desc: "scheduler-policy-NC1", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "NC1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.NC1, + TargetGroup: "target-group-NC1", + }} + + q = cfgplugins.NewQoSSchedulerPolicy(t, dut, q, schedulerPolicies) + + t.Logf("Create QoS output interface config") + schedulerIntfs := []cfgplugins.QoSSchedulerInterface{ + { + Desc: "output-interface-BE1", + QueueName: queues.BE1, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF1", + QueueName: queues.AF1, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF2", + QueueName: queues.AF2, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF3", + QueueName: queues.AF3, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF4", + QueueName: queues.AF4, + Scheduler: "scheduler", + }, { + Desc: "output-interface-NC1", + QueueName: queues.NC1, + Scheduler: "scheduler", + }} + + q = cfgplugins.NewQoSSchedulerInterface(t, dut, q, schedulerIntfs, "port3") + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + +} + +func ConfigureDUTQoSMPLS(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + cfgplugins.NewQoSQueue(t, dut, q) + + t.Logf("Create QoS forwarding groups and queue names configuration") + forwardingGroups := []cfgplugins.ForwardingGroup{ + { + Desc: "forwarding-group-BE1", + QueueName: queues.BE1, + TargetGroup: "target-group-BE1", + Priority: 0, + }, { + Desc: "forwarding-group-AF1", + QueueName: queues.AF1, + TargetGroup: "target-group-AF1", + Priority: 1, + }, { + Desc: "forwarding-group-AF2", + QueueName: queues.AF2, + TargetGroup: "target-group-AF2", + Priority: 2, + }, { + Desc: "forwarding-group-AF3", + QueueName: queues.AF3, + TargetGroup: "target-group-AF3", + Priority: 3, + }, { + Desc: "forwarding-group-AF4", + QueueName: queues.AF4, + TargetGroup: "target-group-AF4", + Priority: 4, + }, { + Desc: "forwarding-group-NC1", + QueueName: queues.NC1, + TargetGroup: "target-group-NC1", + Priority: 5, + }} + + cfgplugins.NewQoSForwardingGroup(t, dut, q, forwardingGroups) + + t.Logf("Create QoS Classifiers config") + classifiers := []cfgplugins.QosClassifier{ + { + Desc: "classifier_mpls_be1", + Name: "dscp_based_classifier_mpls", + ClassType: oc.Qos_Classifier_Type_MPLS, + TermID: "0", + TargetGroup: "target-group-BE1", + DscpSet: []uint8{0}, + }, { + Desc: "classifier_mpls_af1", + Name: "dscp_based_classifier_mpls", + ClassType: oc.Qos_Classifier_Type_MPLS, + TermID: "1", + TargetGroup: "target-group-AF1", + DscpSet: []uint8{1}, + }, { + Desc: "classifier_mpls_af2", + Name: "dscp_based_classifier_mpls", + ClassType: oc.Qos_Classifier_Type_MPLS, + TermID: "2", + TargetGroup: "target-group-AF2", + DscpSet: []uint8{2}, + }, { + Desc: "classifier_mpls_af3", + Name: "dscp_based_classifier_mpls", + ClassType: oc.Qos_Classifier_Type_MPLS, + TermID: "3", + TargetGroup: "target-group-AF3", + DscpSet: []uint8{3}, + }, { + Desc: "classifier_mpls_af4", + Name: "dscp_based_classifier_mpls", + ClassType: oc.Qos_Classifier_Type_MPLS, + TermID: "4", + TargetGroup: "target-group-AF4", + DscpSet: []uint8{4, 5}, + }, { + Desc: "classifier_mpls_nc1", + Name: "dscp_based_classifier_mpls", + ClassType: oc.Qos_Classifier_Type_MPLS, + TermID: "5", + TargetGroup: "target-group-NC1", + DscpSet: []uint8{6, 7}, + }} + + t.Logf("QoS classifiers config: %v", classifiers) + if deviations.MplsExpIngressClassifierOcUnsupported(dut) { + configureMplsExpClassifierCLI(t, dut, classifiers) + } else { + + q = cfgplugins.NewQoSClassifierConfiguration(t, dut, q, classifiers) + + t.Logf("Create QoS input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type MPLS", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_MPLS, + classifier: "dscp_based_classifier_mpls", + }, { + desc: "Input Classifier Type MPLS", + intf: dp2.Name(), + inputClassifierType: oc.Input_Classifier_Type_MPLS, + classifier: "dscp_based_classifier_mpls", + }} + + t.Logf("QoS input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + } + + t.Logf("Create QoS scheduler policies config") + schedulerPolicies := []cfgplugins.SchedulerPolicy{ + { + Desc: "scheduler-policy-BE1", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "BE1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.BE1, + TargetGroup: "target-group-BE1", + }, { + Desc: "scheduler-policy-AF1", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF1, + TargetGroup: "target-group-AF1", + }, { + Desc: "scheduler-policy-AF2", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF2", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF2, + TargetGroup: "target-group-AF2", + }, { + Desc: "scheduler-policy-AF3", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF3", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF3, + TargetGroup: "target-group-AF3", + }, { + Desc: "scheduler-policy-AF4", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "AF4", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.AF4, + TargetGroup: "target-group-AF4", + }, { + Desc: "scheduler-policy-NC1", + Sequence: uint32(0), + SetPriority: true, + SetWeight: false, + Priority: oc.Scheduler_Priority_STRICT, + InputID: "NC1", + InputType: oc.Input_InputType_QUEUE, + QueueName: queues.NC1, + TargetGroup: "target-group-NC1", + }} + + q = cfgplugins.NewQoSSchedulerPolicy(t, dut, q, schedulerPolicies) + + t.Logf("Create QoS output interface config") + schedulerIntfs := []cfgplugins.QoSSchedulerInterface{ + { + Desc: "output-interface-BE1", + QueueName: queues.BE1, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF1", + QueueName: queues.AF1, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF2", + QueueName: queues.AF2, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF3", + QueueName: queues.AF3, + Scheduler: "scheduler", + }, { + Desc: "output-interface-AF4", + QueueName: queues.AF4, + Scheduler: "scheduler", + }, { + Desc: "output-interface-NC1", + QueueName: queues.NC1, + Scheduler: "scheduler", + }} + + q = cfgplugins.NewQoSSchedulerInterface(t, dut, q, schedulerIntfs, "port3") + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + +} + +func configureHardwareInit(t *testing.T, dut *ondatra.DUTDevice) { + hardwareInitCfg := cfgplugins.NewDUTHardwareInit(t, dut, cfgplugins.FeatureMplsTracking) + if hardwareInitCfg == "" { + return + } + cfgplugins.PushDUTHardwareInitConfig(t, dut, hardwareInitCfg) +} + +func buildCliSetRequest(config string) *gpb.SetRequest { + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Origin: "cli", + Elem: []*gpb.PathElem{}, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_AsciiVal{ + AsciiVal: config, + }, + }, + }, + }, + } + return gpbSetRequest +} + +func configureMplsExpClassifierCLI(t *testing.T, dut *ondatra.DUTDevice, classifiers []cfgplugins.QosClassifier) { + + qosMapCmd := fmt.Sprintf(` + mpls ip + ! + mpls static top-label %d %s pop payload-type ipv4 + ! + `, mplsLabel, ateRxP3.IPv4) + qosMapExp := ` + qos map exp %d to traffic-class %s + ! + ` + + for _, classifier := range classifiers { + tc := classifier.TermID + for _, exp := range classifier.DscpSet { + qosMapCmd += fmt.Sprintf(qosMapExp, exp, tc) + t.Logf(qosMapExp, exp, tc) + + } + } + + gnmiClient := dut.RawAPIs().GNMI(t) + t.Logf("Push the CLI QoS config:%s", dut.Vendor()) + gpbSetRequest := buildCliSetRequest(qosMapCmd) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Failed to set QoS Exp mappings from CLI: %v", err) + } +} + +func waitForTraffic(t *testing.T, otg *otg.OTG, flowName string, timeout time.Duration) { + transmitPath := gnmi.OTG().Flow(flowName).Transmit().State() + _, ok := gnmi.Watch(t, otg, transmitPath, timeout, func(val *ygnmi.Value[bool]) bool { + transmitState, present := val.Val() + return present && !transmitState + }).Await(t) + + if !ok { + t.Errorf("Traffic for flow %s did not stop within the timeout of %d", flowName, timeout) + } else { + t.Logf("Traffic for flow %s has stopped", flowName) + } +} diff --git a/feature/qos/otg_tests/egress_strict_priority_scheduler_test/metadata.textproto b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/metadata.textproto new file mode 100644 index 00000000000..9ccc33ecee2 --- /dev/null +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_test/metadata.textproto @@ -0,0 +1,47 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "a6d9700a-2c77-4f31-bffc-52e664b20a79" +plan_id: "DP-1.15" +description: "Egress Strict Priority scheduler" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + ecn_profile_required_definition: true + no_zero_suppression: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + explicit_port_speed: true + explicit_interface_in_default_vrf: true + qos_octets: true + qos_queue_requires_id: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + unsupported_qos_output_service_policy: true + interface_output_queue_non_standard_name: true + mpls_exp_ingress_classifier_oc_unsupported: true + } +} diff --git a/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/egress_strict_priority_scheduler_with_bursty_traffic_test.go b/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/egress_strict_priority_scheduler_with_bursty_traffic_test.go new file mode 100644 index 00000000000..a03e2b2fbaa --- /dev/null +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/egress_strict_priority_scheduler_with_bursty_traffic_test.go @@ -0,0 +1,2640 @@ +// Copyright 2025 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 egress_strict_priority_scheduler_with_bursty_traffic_test + +import ( + "context" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/qoscfg" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ondatra/otg" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +var ( + dutIngressPort1AteP1 = attrs.Attributes{IPv4: "198.51.100.1", IPv4Len: 30, IPv6: "2001:db8::1", IPv6Len: 126} + dutIngressPort2AteP2 = attrs.Attributes{IPv4: "198.51.100.5", IPv4Len: 30, IPv6: "2001:db8::5", IPv6Len: 126} + dutEgressPort3AteP3 = attrs.Attributes{IPv4: "198.51.100.9", IPv4Len: 30, IPv6: "2001:db8::9", IPv6Len: 126} + + ateTxP1 = attrs.Attributes{Name: "ate1", MAC: "00:01:01:01:01:01", IPv4: "198.51.100.2", IPv4Len: 30, IPv6: "2001:db8::2", IPv6Len: 126} + ateTxP2 = attrs.Attributes{Name: "ate2", MAC: "00:01:01:01:01:02", IPv4: "198.51.100.6", IPv4Len: 30, IPv6: "2001:db8::6", IPv6Len: 126} + ateRxP3 = attrs.Attributes{Name: "ate3", MAC: "00:01:01:01:01:03", IPv4: "198.51.100.10", IPv4Len: 30, IPv6: "2001:db8::a", IPv6Len: 126} + + mplsLabel uint32 = 1001 + tolerance float32 = 5.0 +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestEgressStrictPrioritySchedulerBurstTraffic(t *testing.T) { + dut := ondatra.DUT(t, "dut") + + t.Logf("Configuring Hardware Init") + configureHardwareInit(t, dut) + + t.Logf("Configuring QoS Global parameters") + configureQoSGlobalParams(t, dut) + + verifyEgressStrictPrioritySchedulerBurstTrafficIPv4(t, dut) + verifyEgressStrictPrioritySchedulerBurstTrafficIPv6(t, dut) + verifyEgressStrictPrioritySchedulerBurstTrafficMPLS(t, dut) +} + +func verifyEgressStrictPrioritySchedulerBurstTrafficIPv4(t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + // Configure DUT interfaces and QoS. + ConfigureDUTIntfIPv4(t, dut) + ConfigureDUTQoSIPv4(t, dut) + + // Configure ATE interfaces. + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + top := gosnappi.NewConfig() + + ateTxP1.AddToOTG(top, ap1, &dutIngressPort1AteP1) + ateTxP2.AddToOTG(top, ap2, &dutIngressPort2AteP2) + ateRxP3.AddToOTG(top, ap3, &dutEgressPort3AteP3) + ate.OTG().PushConfig(t, top) + + createTrafficFlowsIPv4(t, ate, top, dut) + t.Run("\n*** Running test for IPv4 ***\n", func(t *testing.T) { + + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + queues := netutil.CommonTrafficQueues(t, dut) + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + burstPackets uint32 + burstMinGap uint32 + burstGap uint32 + } + + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-nc1": { + frameSize: 256, + trafficRate: 10, + dscp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af4": { + frameSize: 256, + trafficRate: 20, + dscp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af3": { + frameSize: 256, + trafficRate: 10, + dscp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 15, + expectedThroughputPct: 50.0, + dscp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af2": { + frameSize: 256, + trafficRate: 17, + dscp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af1": { + frameSize: 256, + trafficRate: 13, + dscp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-be1": { + frameSize: 256, + trafficRate: 20, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + } + + if deviations.InterfaceOutputQueueNonStandardName(dut) { + + // Configuring the non-standard queue names. + for flowName, data := range trafficFlows { + if strings.Contains(strings.ToUpper(flowName), "BE1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(0) + } + if strings.Contains(strings.ToUpper(flowName), "AF1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(1) + } + if strings.Contains(strings.ToUpper(flowName), "AF2") { + data.queue = dp3.Name() + "-" + strconv.Itoa(2) + } + if strings.Contains(strings.ToUpper(flowName), "AF3") { + data.queue = dp3.Name() + "-" + strconv.Itoa(3) + } + if strings.Contains(strings.ToUpper(flowName), "AF4") { + data.queue = dp3.Name() + "-" + strconv.Itoa(4) + } + if strings.Contains(strings.ToUpper(flowName), "NC1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(5) + } + } + + } + + ateOutPkts := make(map[string]uint64) + ateInPkts := make(map[string]uint64) + dutQosPktsBeforeTraffic := make(map[string]uint64) + dutQosPktsAfterTraffic := make(map[string]uint64) + dutQosDroppedPktsBeforeTraffic := make(map[string]uint64) + dutQosDroppedPktsAfterTraffic := make(map[string]uint64) + + // Set the initial counters to 0. + for _, data := range trafficFlows { + ateOutPkts[data.queue] = 0 + ateInPkts[data.queue] = 0 + dutQosPktsBeforeTraffic[data.queue] = 0 + dutQosPktsAfterTraffic[data.queue] = 0 + dutQosDroppedPktsBeforeTraffic[data.queue] = 0 + dutQosDroppedPktsAfterTraffic[data.queue] = 0 + } + + uniqueQueues := make(map[string]bool) + for _, data := range trafficFlows { + uniqueQueues[data.queue] = true + } + + // Get QoS egress packet counters before the traffic. + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + for queue := range uniqueQueues { + count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("TransmitPkts count for queue %s on interface %q not available within %v", queue, dp3.Name(), timeout) + } + dutQosPktsBeforeTraffic[queue], _ = count.Val() + + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedPkts count for queue %s on interface %q not available within %v", dp3.Name(), queue, timeout) + } + dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val() + } + + t.Logf("Before TX values map: %v\n", dutQosPktsBeforeTraffic) + t.Logf("Before DROP values map: %v\n", dutQosDroppedPktsBeforeTraffic) + + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp1.Name(), dp3.Name()) + t.Logf("Running bursty traffic on DUT interfaces: %s => %s \n", dp2.Name(), dp3.Name()) + t.Logf("Sending traffic flows:\n") + + ate.OTG().StartTraffic(t) + + time.Sleep(15 * time.Second) + + ate.OTG().StopTraffic(t) + + for flowName := range trafficFlows { + waitForTraffic(t, ate.OTG(), flowName, 10) + } + + t.Logf("Printing aggregated flow metrics from OTG: \n") + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for trafficID, data := range trafficFlows { + + t.Logf("Retrieving statistics for %s\n", trafficID) + ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) + ateRxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().InPkts().State()) + if ateTxPkts == 0 { + t.Fatalf("Flow %s did not send any packets, please check tester configuration.\n", trafficID) + } + + lossPct := (float32)((float64(ateTxPkts-ateRxPkts) * 100.0) / float64(ateTxPkts)) + + t.Logf("Flow: %s\t\t| EgressQueue:%s\t\t| Loss%%: %.2f%%\t\t| Rx%%:%.2f%%\t\t| Tolerance%%: %.2f%%", trafficID, data.queue, lossPct, 100-lossPct, tolerance) + + if got, want := 100.0-lossPct, data.expectedThroughputPct; got < want-tolerance || got > want+tolerance { + t.Errorf("Expected throughput for queue %q should be within [%.2f%%, %.2f%%]: got %.2f%%\n", data.queue, want-tolerance, want+tolerance, got) + } + + ateOutPkts[data.queue] += ateTxPkts + ateInPkts[data.queue] += ateRxPkts + + } + + header := "| %-20s | %-20s | %-18s | %-18s | %-24s | %-24s |" + row := "| %-20s | %-20s | %-18d | %-18d | %-24d | %-24d |" + t.Logf(header, "Intf", "Queue", "ATE Tx frames", "DUT Tx frames", "ATE Dropped frames", "DUT Dropped frames") + + for queue := range uniqueQueues { + dutQueueTransmitPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State()) + dutQueueDroppedPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State()) + + dutQueueTransmitPkts := dutQueueTransmitPktsTotal - dutQosPktsBeforeTraffic[queue] + dutQueueDroppedPkts := dutQueueDroppedPktsTotal - dutQosDroppedPktsBeforeTraffic[queue] + + ateQueueTransmitPkts := ateOutPkts[queue] + var ateQueueDroppedPkts uint64 = 0 + if ateOutPkts[queue] >= ateInPkts[queue] { + ateQueueDroppedPkts = ateOutPkts[queue] - ateInPkts[queue] + } else { + t.Fatalf("ATE reports more received pkts than sent pkts on interface %s and egress queue %s", dp3.Name(), queue) + } + t.Logf(row, dp3.Name(), queue, ateQueueTransmitPkts, dutQueueTransmitPkts, ateQueueDroppedPkts, dutQueueDroppedPkts) + } + + }) + +} + +func verifyEgressStrictPrioritySchedulerBurstTrafficIPv6(t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + // Configure DUT interfaces and QoS. + ConfigureDUTIntfIPv6(t, dut) + ConfigureDUTQoSIPv6(t, dut) + + // Configure ATE interfaces. + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + top := gosnappi.NewConfig() + + ateTxP1.AddToOTG(top, ap1, &dutIngressPort1AteP1) + ateTxP2.AddToOTG(top, ap2, &dutIngressPort2AteP2) + ateRxP3.AddToOTG(top, ap3, &dutEgressPort3AteP3) + ate.OTG().PushConfig(t, top) + + createTrafficFlowsIPv6(t, ate, top, dut) + t.Run("\n*** Running test for IPv6 ***\n", func(t *testing.T) { + + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), top, "IPv6") + + queues := netutil.CommonTrafficQueues(t, dut) + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp []uint32 + queue string + inputIntf attrs.Attributes + burstPackets uint32 + burstMinGap uint32 + burstGap uint32 + } + + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: []uint32{48, 49, 50, 51, 52, 53, 54, 55}, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-nc1": { + frameSize: 256, + trafficRate: 10, + dscp: []uint32{56, 57, 58, 59, 60, 61, 62, 63}, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: []uint32{32, 33, 34, 35, 36, 37, 38, 39}, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af4": { + frameSize: 256, + trafficRate: 20, + dscp: []uint32{40, 41, 42, 43, 44, 45, 46, 47}, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: []uint32{24, 25, 26, 27}, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af3": { + frameSize: 256, + trafficRate: 10, + dscp: []uint32{28, 29, 30, 31}, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 15, + expectedThroughputPct: 50.0, + dscp: []uint32{16, 17, 18, 19}, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af2": { + frameSize: 256, + trafficRate: 17, + dscp: []uint32{20, 21, 22, 23}, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{8, 9, 10, 11}, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af1": { + frameSize: 256, + trafficRate: 13, + dscp: []uint32{12, 13, 14, 15}, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{0, 1, 2, 3}, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-be1": { + frameSize: 512, + trafficRate: 20, + expectedThroughputPct: 0.0, + dscp: []uint32{4, 5, 6, 7}, + queue: queues.BE1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + } + + if deviations.InterfaceOutputQueueNonStandardName(dut) { + + // Configuring the non-standard queue names. + for flowName, data := range trafficFlows { + if strings.Contains(strings.ToUpper(flowName), "BE1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(0) + } + if strings.Contains(strings.ToUpper(flowName), "AF1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(1) + } + if strings.Contains(strings.ToUpper(flowName), "AF2") { + data.queue = dp3.Name() + "-" + strconv.Itoa(2) + } + if strings.Contains(strings.ToUpper(flowName), "AF3") { + data.queue = dp3.Name() + "-" + strconv.Itoa(3) + } + if strings.Contains(strings.ToUpper(flowName), "AF4") { + data.queue = dp3.Name() + "-" + strconv.Itoa(4) + } + if strings.Contains(strings.ToUpper(flowName), "NC1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(5) + } + } + } + + ateOutPkts := make(map[string]uint64) + ateInPkts := make(map[string]uint64) + dutQosPktsBeforeTraffic := make(map[string]uint64) + dutQosPktsAfterTraffic := make(map[string]uint64) + dutQosDroppedPktsBeforeTraffic := make(map[string]uint64) + dutQosDroppedPktsAfterTraffic := make(map[string]uint64) + + // Set the initial counters to 0. + for _, data := range trafficFlows { + ateOutPkts[data.queue] = 0 + ateInPkts[data.queue] = 0 + dutQosPktsBeforeTraffic[data.queue] = 0 + dutQosPktsAfterTraffic[data.queue] = 0 + dutQosDroppedPktsBeforeTraffic[data.queue] = 0 + dutQosDroppedPktsAfterTraffic[data.queue] = 0 + } + + uniqueQueues := make(map[string]bool) + for _, data := range trafficFlows { + uniqueQueues[data.queue] = true + } + // Get QoS egress packet counters before the traffic. + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + for queue := range uniqueQueues { + count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("TransmitPkts count for queue %s on interface %q not available within %v", queue, dp3.Name(), timeout) + } + dutQosPktsBeforeTraffic[queue], _ = count.Val() + + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedPkts count for queue %s on interface %q not available within %v", dp3.Name(), queue, timeout) + } + dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val() + } + + t.Logf("Before TX values map: %v\n", dutQosPktsBeforeTraffic) + t.Logf("Before DROP values map: %v\n", dutQosDroppedPktsBeforeTraffic) + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp1.Name(), dp3.Name()) + t.Logf("Running bursty traffic on DUT interfaces: %s => %s \n", dp2.Name(), dp3.Name()) + t.Logf("Sending traffic flows:\n") + ate.OTG().StartTraffic(t) + time.Sleep(30 * time.Second) + ate.OTG().StopTraffic(t) + + time.Sleep(5 * time.Second) + + t.Logf("Printing aggregated flow metrics from OTG: \n") + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for trafficID, data := range trafficFlows { + + t.Logf("Retrieving statistics for %s\n", trafficID) + ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) + ateRxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().InPkts().State()) + if ateTxPkts == 0 { + t.Fatalf("Flow %s did not send any packets, please check tester configuration.\n", trafficID) + } + + lossPct := (float32)((float64(ateTxPkts-ateRxPkts) * 100.0) / float64(ateTxPkts)) + + t.Logf("Flow: %s\t\t|EgressQueue:%s\t\t|Loss%%: %.2f%%\t\t|ExpectedRx%%:%.2f%%\t\t|Tolerance%%: %.2f%%", trafficID, data.queue, lossPct, 100-lossPct, tolerance) + + if got, want := 100.0-lossPct, data.expectedThroughputPct; got < want-tolerance || got > want+tolerance { + t.Errorf("Expected throughput for queue %q should be within [%.2f%%, %.2f%%]: got %.2f%%\n", data.queue, want-tolerance, want+tolerance, got) + } + + ateOutPkts[data.queue] += ateTxPkts + ateInPkts[data.queue] += ateRxPkts + + } + + header := "| %-20s | %-20s | %-18s | %-18s | %-24s | %-24s |" + row := "| %-20s | %-20s | %-18d | %-18d | %-24d | %-24d |" + t.Logf(header, "Intf", "Queue", "ATE Tx frames", "DUT Tx frames", "ATE Dropped frames", "DUT Dropped frames") + + for queue := range uniqueQueues { + dutQueueTransmitPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State()) + dutQueueDroppedPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State()) + + dutQueueTransmitPkts := dutQueueTransmitPktsTotal - dutQosPktsBeforeTraffic[queue] + dutQueueDroppedPkts := dutQueueDroppedPktsTotal - dutQosDroppedPktsBeforeTraffic[queue] + + ateQueueTransmitPkts := ateOutPkts[queue] + var ateQueueDroppedPkts uint64 = 0 + if ateOutPkts[queue] >= ateInPkts[queue] { + ateQueueDroppedPkts = ateOutPkts[queue] - ateInPkts[queue] + } else { + t.Fatalf("ATE reports more received pkts than sent pkts on interface %s and egress queue %s", dp3.Name(), queue) + } + t.Logf(row, dp3.Name(), queue, ateQueueTransmitPkts, dutQueueTransmitPkts, ateQueueDroppedPkts, dutQueueDroppedPkts) + } + + }) + +} + +func verifyEgressStrictPrioritySchedulerBurstTrafficMPLS(t *testing.T, dut *ondatra.DUTDevice) { + + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + // Configure DUT interfaces and QoS. + ConfigureDUTIntfIPv4(t, dut) + ConfigureDUTQoSMPLS(t, dut) + + // Configure ATE interfaces. + ate := ondatra.ATE(t, "ate") + ap1 := ate.Port(t, "port1") + ap2 := ate.Port(t, "port2") + ap3 := ate.Port(t, "port3") + + top := gosnappi.NewConfig() + + ateTxP1.AddToOTG(top, ap1, &dutIngressPort1AteP1) + ateTxP2.AddToOTG(top, ap2, &dutIngressPort2AteP2) + ateRxP3.AddToOTG(top, ap3, &dutEgressPort3AteP3) + ate.OTG().PushConfig(t, top) + + t.Run("\n*** Running test for MPLS ***\n", func(t *testing.T) { + + ate.OTG().StartProtocols(t) + + otgutils.WaitForARP(t, ate.OTG(), top, "IPv4") + + createTrafficFlowsMPLS(t, ate, top, dut) + + queues := netutil.CommonTrafficQueues(t, dut) + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + burstPackets uint32 + burstMinGap uint32 + burstGap uint32 + } + + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-nc1": { + frameSize: 256, + trafficRate: 10, + dscp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af4": { + frameSize: 256, + trafficRate: 20, + dscp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af3": { + frameSize: 256, + trafficRate: 10, + dscp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 15, + expectedThroughputPct: 50.0, + dscp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af2": { + frameSize: 256, + trafficRate: 17, + dscp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af1": { + frameSize: 256, + trafficRate: 13, + dscp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-be1": { + frameSize: 256, + trafficRate: 20, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + } + + if deviations.InterfaceOutputQueueNonStandardName(dut) { + + // Configuring the non-standard queue names. + for flowName, data := range trafficFlows { + if strings.Contains(strings.ToUpper(flowName), "BE1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(0) + } + if strings.Contains(strings.ToUpper(flowName), "AF1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(1) + } + if strings.Contains(strings.ToUpper(flowName), "AF2") { + data.queue = dp3.Name() + "-" + strconv.Itoa(2) + } + if strings.Contains(strings.ToUpper(flowName), "AF3") { + data.queue = dp3.Name() + "-" + strconv.Itoa(3) + } + if strings.Contains(strings.ToUpper(flowName), "AF4") { + data.queue = dp3.Name() + "-" + strconv.Itoa(4) + } + if strings.Contains(strings.ToUpper(flowName), "NC1") { + data.queue = dp3.Name() + "-" + strconv.Itoa(5) + } + } + + } + ateOutPkts := make(map[string]uint64) + ateInPkts := make(map[string]uint64) + dutQosPktsBeforeTraffic := make(map[string]uint64) + dutQosPktsAfterTraffic := make(map[string]uint64) + dutQosDroppedPktsBeforeTraffic := make(map[string]uint64) + dutQosDroppedPktsAfterTraffic := make(map[string]uint64) + + // Set the initial counters to 0. + for _, data := range trafficFlows { + ateOutPkts[data.queue] = 0 + ateInPkts[data.queue] = 0 + dutQosPktsBeforeTraffic[data.queue] = 0 + dutQosPktsAfterTraffic[data.queue] = 0 + dutQosDroppedPktsBeforeTraffic[data.queue] = 0 + dutQosDroppedPktsAfterTraffic[data.queue] = 0 + } + + uniqueQueues := make(map[string]bool) + for _, data := range trafficFlows { + uniqueQueues[data.queue] = true + } + + // Get QoS egress packet counters before the traffic. + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + for queue := range uniqueQueues { + count, ok := gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("TransmitPkts count for queue %s on interface %q not available within %v", queue, dp3.Name(), timeout) + } + dutQosPktsBeforeTraffic[queue], _ = count.Val() + + count, ok = gnmi.Watch(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("DroppedPkts count for queue %s on interface %q not available within %v", dp3.Name(), queue, timeout) + } + dutQosDroppedPktsBeforeTraffic[queue], _ = count.Val() + } + + t.Logf("Running regular traffic on DUT interfaces: %s => %s \n", dp1.Name(), dp3.Name()) + t.Logf("Running bursty traffic on DUT interfaces: %s => %s \n", dp2.Name(), dp3.Name()) + t.Logf("Sending traffic flows:\n") + ate.OTG().StartTraffic(t) + + time.Sleep(15 * time.Second) + + ate.OTG().StopTraffic(t) + + time.Sleep(5 * time.Second) + + t.Logf("Printing aggregated flow metrics from OTG: \n") + otgutils.LogFlowMetrics(t, ate.OTG(), top) + + for trafficID, data := range trafficFlows { + + t.Logf("Retrieving statistics for %s\n", trafficID) + ateTxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().OutPkts().State()) + ateRxPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(trafficID).Counters().InPkts().State()) + if ateTxPkts == 0 { + t.Fatalf("Flow %s did not send any packets, please check tester configuration.\n", trafficID) + } + + lossPct := (float32)((float64(ateTxPkts-ateRxPkts) * 100.0) / float64(ateTxPkts)) + + t.Logf("Flow: %s\t\t|EgressQueue:%s\t\t|Loss%%: %.2f%%\t\t|ExpectedRx%%:%.2f%%\t\t|Tolerance%%: %.2f%%", trafficID, data.queue, lossPct, 100-lossPct, tolerance) + + if got, want := 100.0-lossPct, data.expectedThroughputPct; got < want-tolerance || got > want+tolerance { + t.Errorf("Expected throughput for queue %q should be within [%.2f%%, %.2f%%]: got %.2f%%\n", data.queue, want-tolerance, want+tolerance, got) + } + + ateOutPkts[data.queue] += ateTxPkts + ateInPkts[data.queue] += ateRxPkts + + } + + header := "| %-20s | %-20s | %-18s | %-18s | %-24s | %-24s |" + row := "| %-20s | %-20s | %-18d | %-18d | %-24d | %-24d |" + t.Logf(header, "Intf", "Queue", "ATE Tx frames", "DUT Tx frames", "ATE Dropped frames", "DUT Dropped frames") + + for queue := range uniqueQueues { + dutQueueTransmitPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).TransmitPkts().State()) + dutQueueDroppedPktsTotal := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp3.Name()).Output().Queue(queue).DroppedPkts().State()) + + dutQueueTransmitPkts := dutQueueTransmitPktsTotal - dutQosPktsBeforeTraffic[queue] + dutQueueDroppedPkts := dutQueueDroppedPktsTotal - dutQosDroppedPktsBeforeTraffic[queue] + + ateQueueTransmitPkts := ateOutPkts[queue] + var ateQueueDroppedPkts uint64 = 0 + if ateOutPkts[queue] >= ateInPkts[queue] { + ateQueueDroppedPkts = ateOutPkts[queue] - ateInPkts[queue] + } else { + t.Fatalf("ATE reports more received pkts than sent pkts on interface %s and egress queue %s", dp3.Name(), queue) + } + t.Logf(row, dp3.Name(), queue, ateQueueTransmitPkts, dutQueueTransmitPkts, ateQueueDroppedPkts, dutQueueDroppedPkts) + } + + }) + +} + +func createTrafficFlowsIPv4(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, dut *ondatra.DUTDevice) { + t.Helper() + // configuration of regular and burst flows on the ATE + /* + Non-burst flows on ateTxP1: + + Forwarding Group Traffic linerate (%) Frame size Expected Loss % + be1 12 512 100 + af1 12 512 100 + af2 15 512 50 + af3 12 512 0 + af4 30 512 0 + nc1 1 512 0 + + Burst flows on ateTxP2: + + Fwd Grp | Traffic linerate (%) | FS | Burst | IPG | IBG | Expected loss (%) + be1 | 20 | 256 | 50000 | 12 | 100 | 100 + af1 | 13 | 256 | 50000 | 12 | 100 | 100 + af2 | 17 | 256 | 50000 | 12 | 100 | 50 + af3 | 10 | 256 | 50000 | 12 | 100 | 0 + af4 | 20 | 256 | 50000 | 12 | 100 | 0 + nc1 | 10 | 256 | 50000 | 12 | 100 | 0 + */ + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + burstPackets uint32 + burstMinGap uint32 + burstGap uint32 + } + + queues := netutil.CommonTrafficQueues(t, dut) + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-nc1": { + frameSize: 256, + trafficRate: 10, + dscp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af4": { + frameSize: 256, + trafficRate: 20, + dscp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af3": { + frameSize: 256, + trafficRate: 10, + dscp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 15, + expectedThroughputPct: 50.0, + dscp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af2": { + frameSize: 256, + trafficRate: 17, + dscp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af1": { + frameSize: 256, + trafficRate: 13, + dscp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-be1": { + frameSize: 256, + trafficRate: 20, + expectedThroughputPct: 0.0, + dscp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + } + top.Flows().Clear() + + for trafficID, data := range trafficFlows { + t.Logf("Configuring flow %s\n", trafficID) + flow := top.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{data.inputIntf.Name + ".IPv4"}).SetRxNames([]string{ateRxP3.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(data.inputIntf.IPv4) + ipHeader.Dst().SetValue(ateRxP3.IPv4) + ipHeader.Priority().Dscp().Phb().SetValue(uint32(data.dscp)) + + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPercentage(float32(data.trafficRate)) + if data.burstMinGap > 0 { + flow.Duration().Burst().SetPackets(uint32(data.burstPackets)).SetGap(uint32(data.burstMinGap)) + } + if data.burstGap > 0 { + flow.Duration().Burst().InterBurstGap().SetBytes(float64(data.burstGap)) + } + + } + ate.OTG().PushConfig(t, top) +} + +func createTrafficFlowsIPv6(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, dut *ondatra.DUTDevice) { + t.Helper() + // configuration of regular and burst flows on the ATE + /* + Non-burst flows on ateTxP1: + + Forwarding Group Traffic linerate (%) Frame size Expected Loss % + be1 12 512 100 + af1 12 512 100 + af2 15 512 50 + af3 12 512 0 + af4 30 512 0 + nc1 1 512 0 + + Burst flows on ateTxP2: + + Fwd Grp | Traffic linerate (%) | FS | Burst | IPG | IBG | Expected loss (%) + be1 | 20 | 256 | 50000 | 12 | 100 | 100 + af1 | 13 | 256 | 50000 | 12 | 100 | 100 + af2 | 17 | 256 | 50000 | 12 | 100 | 50 + af3 | 10 | 256 | 50000 | 12 | 100 | 0 + af4 | 20 | 256 | 50000 | 12 | 100 | 0 + nc1 | 10 | 256 | 50000 | 12 | 100 | 0 + */ + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + dscp []uint32 + queue string + inputIntf attrs.Attributes + burstPackets uint32 + burstMinGap uint32 + burstGap uint32 + } + + queues := netutil.CommonTrafficQueues(t, dut) + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + dscp: []uint32{48, 49, 50, 51, 52, 53, 54, 55}, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-nc1": { + frameSize: 256, + trafficRate: 10, + dscp: []uint32{56, 57, 58, 59, 60, 61, 62, 63}, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + dscp: []uint32{32, 33, 34, 35, 36, 37, 38, 39}, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af4": { + frameSize: 256, + trafficRate: 20, + dscp: []uint32{40, 41, 42, 43, 44, 45, 46, 47}, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + dscp: []uint32{24, 25, 26, 27}, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af3": { + frameSize: 256, + trafficRate: 10, + dscp: []uint32{28, 29, 30, 31}, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 15, + expectedThroughputPct: 50.0, + dscp: []uint32{16, 17, 18, 19}, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af2": { + frameSize: 256, + trafficRate: 17, + dscp: []uint32{20, 21, 22, 23}, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{8, 9, 10, 11}, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af1": { + frameSize: 256, + trafficRate: 13, + dscp: []uint32{12, 13, 14, 15}, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + dscp: []uint32{0, 1, 2, 3}, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-be1": { + frameSize: 512, + trafficRate: 20, + expectedThroughputPct: 0.0, + dscp: []uint32{4, 5, 6, 7}, + queue: queues.BE1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + } + top.Flows().Clear() + + for trafficID, data := range trafficFlows { + t.Logf("Configuring flow %s\n", trafficID) + flow := top.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{data.inputIntf.Name + ".IPv6"}).SetRxNames([]string{ateRxP3.Name + ".IPv6"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + + ipHeader := flow.Packet().Add().Ipv6() + ipHeader.Src().SetValue(data.inputIntf.IPv6) + ipHeader.Dst().SetValue(ateRxP3.IPv6) + ipHeader.TrafficClass().SetValues([]uint32(data.dscp)) + + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPercentage(float32(data.trafficRate)) + if data.burstMinGap > 0 { + flow.Duration().Burst().SetPackets(uint32(data.burstPackets)).SetGap(uint32(data.burstMinGap)) + } + if data.burstGap > 0 { + flow.Duration().Burst().InterBurstGap().SetBytes(float64(data.burstGap)) + } + + } + ate.OTG().PushConfig(t, top) +} + +func createTrafficFlowsMPLS(t *testing.T, ate *ondatra.ATEDevice, top gosnappi.Config, dut *ondatra.DUTDevice) { + t.Helper() + // configuration of regular and burst flows on the ATE + /* + Non-burst flows on ateTxP1: + + Forwarding Group Traffic linerate (%) Frame size Expected Loss % + be1 12 512 100 + af1 12 512 100 + af2 15 512 50 + af3 12 512 0 + af4 30 512 0 + nc1 1 512 0 + + Burst flows on ateTxP2: + + Fwd Grp | Traffic linerate (%) | FS | Burst | IPG | IBG | Expected loss (%) + be1 | 20 | 256 | 50000 | 12 | 100 | 100 + af1 | 13 | 256 | 50000 | 12 | 100 | 100 + af2 | 17 | 256 | 50000 | 12 | 100 | 50 + af3 | 10 | 256 | 50000 | 12 | 100 | 0 + af4 | 20 | 256 | 50000 | 12 | 100 | 0 + nc1 | 10 | 256 | 50000 | 12 | 100 | 0 + */ + + type trafficData struct { + trafficRate float64 + expectedThroughputPct float32 + frameSize uint32 + exp uint8 + queue string + inputIntf attrs.Attributes + burstPackets uint32 + burstMinGap uint32 + burstGap uint32 + } + + queues := netutil.CommonTrafficQueues(t, dut) + trafficFlows := map[string]*trafficData{ + "ateTxP1-regular-nc1": { + frameSize: 512, + trafficRate: 1, + expectedThroughputPct: 100.0, + exp: 6, + queue: queues.NC1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-nc1": { + frameSize: 256, + trafficRate: 10, + exp: 7, + expectedThroughputPct: 100.0, + queue: queues.NC1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af4": { + frameSize: 512, + trafficRate: 30, + expectedThroughputPct: 100.0, + exp: 4, + queue: queues.AF4, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af4": { + frameSize: 256, + trafficRate: 20, + exp: 5, + expectedThroughputPct: 100.0, + queue: queues.AF4, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af3": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 100.0, + exp: 3, + queue: queues.AF3, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af3": { + frameSize: 256, + trafficRate: 10, + exp: 3, + expectedThroughputPct: 100.0, + queue: queues.AF3, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af2": { + frameSize: 512, + trafficRate: 15, + expectedThroughputPct: 50.0, + exp: 2, + queue: queues.AF2, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af2": { + frameSize: 256, + trafficRate: 17, + exp: 2, + expectedThroughputPct: 50.0, + queue: queues.AF2, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-af1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + exp: 1, + queue: queues.AF1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-af1": { + frameSize: 256, + trafficRate: 13, + exp: 1, + expectedThroughputPct: 0.0, + queue: queues.AF1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + "ateTxP1-regular-be1": { + frameSize: 512, + trafficRate: 12, + expectedThroughputPct: 0.0, + exp: 0, + queue: queues.BE1, + inputIntf: ateTxP1, + }, + "ateTxP2-burst-be1": { + frameSize: 256, + trafficRate: 20, + expectedThroughputPct: 0.0, + exp: 0, + queue: queues.BE1, + inputIntf: ateTxP2, + burstPackets: 50000, + burstMinGap: 12, + burstGap: 100, + }, + } + top.Flows().Clear() + + dstMac1 := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + dstMac2 := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port2").Name()).Ethernet().MacAddress().State()) + + for trafficID, data := range trafficFlows { + t.Logf("Configuring flow %s\n", trafficID) + + dstMac := "" + srcPort := "" + + if strings.Contains(strings.ToUpper(trafficID), "REGULAR") { + dstMac = dstMac1 + srcPort = "port1" + } else if strings.Contains(strings.ToUpper(trafficID), "BURST") { + dstMac = dstMac2 + srcPort = "port2" + } + + flow := top.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Port().SetTxName(srcPort).SetRxNames([]string{"port3"}) + + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + ethHeader.Dst().SetValue(dstMac) + + mplsHeader := flow.Packet().Add().Mpls() + mplsHeader.Label().SetValue(mplsLabel) + mplsHeader.TrafficClass().SetValue(uint32(data.exp)) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(data.inputIntf.IPv4) + ipHeader.Dst().SetValue(ateRxP3.IPv4) + + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPercentage(float32(data.trafficRate)) + if data.burstMinGap > 0 { + flow.Duration().Burst().SetPackets(uint32(data.burstPackets)).SetGap(uint32(data.burstMinGap)) + } + if data.burstGap > 0 { + flow.Duration().Burst().InterBurstGap().SetBytes(float64(data.burstGap)) + } + + } + + ate.OTG().PushConfig(t, top) +} + +func ConfigureDUTIntfIPv4(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + dutIntfs := []struct { + desc string + intfName string + ipAddr string + prefixLen uint8 + }{{ + desc: "DUT input intf port1", + intfName: dp1.Name(), + ipAddr: dutIngressPort1AteP1.IPv4, + prefixLen: dutIngressPort1AteP1.IPv4Len, + }, { + desc: "DUT input intf port2", + intfName: dp2.Name(), + ipAddr: dutIngressPort2AteP2.IPv4, + prefixLen: dutIngressPort2AteP2.IPv4Len, + }, { + desc: "DUT output intf port3", + intfName: dp3.Name(), + ipAddr: dutEgressPort3AteP3.IPv4, + prefixLen: dutEgressPort3AteP3.IPv4Len, + }} + + // Configure the interfaces. + for _, intf := range dutIntfs { + t.Logf("Configure DUT interface %s with attributes %v", intf.intfName, intf) + i := &oc.Interface{ + Name: ygot.String(intf.intfName), + Description: ygot.String(intf.desc), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(true), + } + i.GetOrCreateEthernet() + s := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s.Enabled = ygot.Bool(true) + t.Logf("DUT %s %s %s requires interface enable deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + a := s.GetOrCreateAddress(intf.ipAddr) + a.PrefixLength = ygot.Uint8(intf.prefixLen) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf.intfName).Config(), i) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, intf.intfName, deviations.DefaultNetworkInstance(dut), 0) + t.Logf("DUT %s %s %s requires explicit interface in default VRF deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + } + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dp1) + fptest.SetPortSpeed(t, dp2) + fptest.SetPortSpeed(t, dp3) + t.Logf("DUT %s %s %s requires explicit port speed set deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } +} + +func ConfigureDUTIntfIPv6(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + + dutIntfs := []struct { + desc string + intfName string + ipAddr string + prefixLen uint8 + }{{ + desc: "DUT input intf port1", + intfName: dp1.Name(), + ipAddr: dutIngressPort1AteP1.IPv6, + prefixLen: dutIngressPort1AteP1.IPv6Len, + }, { + desc: "DUT input intf port2", + intfName: dp2.Name(), + ipAddr: dutIngressPort2AteP2.IPv6, + prefixLen: dutIngressPort2AteP2.IPv6Len, + }, { + desc: "DUT output intf port3", + intfName: dp3.Name(), + ipAddr: dutEgressPort3AteP3.IPv6, + prefixLen: dutEgressPort3AteP3.IPv6Len, + }} + + // Configure the interfaces. + for _, intf := range dutIntfs { + t.Logf("Configure DUT interface %s with attributes %v", intf.intfName, intf) + i := &oc.Interface{ + Name: ygot.String(intf.intfName), + Description: ygot.String(intf.desc), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(true), + } + i.GetOrCreateEthernet() + s4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + s := i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + s.Enabled = ygot.Bool(true) + a := s.GetOrCreateAddress(intf.ipAddr) + a.PrefixLength = ygot.Uint8(intf.prefixLen) + gnmi.Replace(t, dut, gnmi.OC().Interface(intf.intfName).Config(), i) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, intf.intfName, deviations.DefaultNetworkInstance(dut), 0) + t.Logf("DUT %s %s %s requires explicit interface in default VRF deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } + } + if deviations.ExplicitPortSpeed(dut) { + fptest.SetPortSpeed(t, dp1) + fptest.SetPortSpeed(t, dp2) + fptest.SetPortSpeed(t, dp3) + t.Logf("DUT %s %s %s requires explicit port speed set deviation ", dut.Vendor(), dut.Model(), dut.Version()) + } +} + +func ConfigureDUTQoSIPv4(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + if deviations.QOSQueueRequiresID(dut) { + queueNames := []string{queues.NC1, queues.AF4, queues.AF3, queues.AF2, queues.AF1, queues.BE1} + for i, queue := range queueNames { + q1 := q.GetOrCreateQueue(queue) + q1.Name = ygot.String(queue) + queueid := len(queueNames) - i + q1.QueueId = ygot.Uint8(uint8(queueid)) + } + t.Logf("\nDUT %s %s %s requires QoS queue requires ID deviation \n\n", dut.Vendor(), dut.Model(), dut.Version()) + } + + t.Logf("Create QoS forwarding groups and queue names configuration") + forwardingGroups := []struct { + desc string + queueName string + targetGroup string + priority uint8 + }{{ + desc: "forwarding-group-BE1", + queueName: queues.BE1, + targetGroup: "target-group-BE1", + priority: 0, + }, { + desc: "forwarding-group-AF1", + queueName: queues.AF1, + targetGroup: "target-group-AF1", + priority: 1, + }, { + desc: "forwarding-group-AF2", + queueName: queues.AF2, + targetGroup: "target-group-AF2", + priority: 2, + }, { + desc: "forwarding-group-AF3", + queueName: queues.AF3, + targetGroup: "target-group-AF3", + priority: 3, + }, { + desc: "forwarding-group-AF4", + queueName: queues.AF4, + targetGroup: "target-group-AF4", + priority: 4, + }, { + desc: "forwarding-group-NC1", + queueName: queues.NC1, + targetGroup: "target-group-NC1", + priority: 5, + }} + + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + for _, tc := range forwardingGroups { + qoscfg.SetForwardingGroup(t, dut, q, tc.targetGroup, tc.queueName) + + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + } + + t.Logf("Create QoS Classifiers config") + classifiers := []struct { + desc string + name string + classType oc.E_Qos_Classifier_Type + termID string + targetGroup string + dscpSet []uint8 + }{{ + desc: "classifier_ipv4_be1", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "0", + targetGroup: "target-group-BE1", + dscpSet: []uint8{0}, + }, { + desc: "classifier_ipv4_af1", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "1", + targetGroup: "target-group-AF1", + dscpSet: []uint8{1}, + }, { + desc: "classifier_ipv4_af2", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "2", + targetGroup: "target-group-AF2", + dscpSet: []uint8{2}, + }, { + desc: "classifier_ipv4_af3", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "3", + targetGroup: "target-group-AF3", + dscpSet: []uint8{3}, + }, { + desc: "classifier_ipv4_af4", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "4", + targetGroup: "target-group-AF4", + dscpSet: []uint8{4, 5}, + }, { + desc: "classifier_ipv4_nc1", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "5", + targetGroup: "target-group-NC1", + dscpSet: []uint8{6, 7}, + }} + + t.Logf("QoS classifiers config: %v", classifiers) + for _, tc := range classifiers { + classifier := q.GetOrCreateClassifier(tc.name) + classifier.SetName(tc.name) + classifier.SetType(tc.classType) + + term, err := classifier.NewTerm(tc.termID) + if err != nil { + t.Fatalf("Failed to create classifier.NewTerm(): %v", err) + } + + term.SetId(tc.termID) + action := term.GetOrCreateActions() + action.SetTargetGroup(tc.targetGroup) + condition := term.GetOrCreateConditions() + condition.GetOrCreateIpv4().SetDscpSet(tc.dscpSet) + + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create QoS input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type IPV4", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV4, + classifier: "dscp_based_classifier_ipv4", + }, { + desc: "Input Classifier Type IPV4", + intf: dp2.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV4, + classifier: "dscp_based_classifier_ipv4", + }} + + t.Logf("QoS input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + + t.Logf("Create QoS scheduler policies config") + schedulerPolicies := []struct { + desc string + sequence uint32 + setPriority bool + priority oc.E_Scheduler_Priority + inputID string + inputType oc.E_Input_InputType + setWeight bool + queueName string + targetGroup string + }{{ + desc: "scheduler-policy-BE1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "BE1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.BE1, + targetGroup: "target-group-BE1", + }, { + desc: "scheduler-policy-AF1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF1, + targetGroup: "target-group-AF1", + }, { + desc: "scheduler-policy-AF2", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF2", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF2, + targetGroup: "target-group-AF2", + }, { + desc: "scheduler-policy-AF3", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF3", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF3, + targetGroup: "target-group-AF3", + }, { + desc: "scheduler-policy-AF4", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF4", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF4, + targetGroup: "target-group-AF4", + }, { + desc: "scheduler-policy-NC1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "NC1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.NC1, + targetGroup: "target-group-NC1", + }} + + schedulerPolicy := q.GetOrCreateSchedulerPolicy("scheduler") + schedulerPolicy.SetName("scheduler") + t.Logf("QoS scheduler policies config: %v", schedulerPolicies) + for _, tc := range schedulerPolicies { + s := schedulerPolicy.GetOrCreateScheduler(tc.sequence) + s.SetSequence(tc.sequence) + if tc.setPriority { + s.SetPriority(tc.priority) + } + input := s.GetOrCreateInput(tc.inputID) + input.SetId(tc.inputID) + input.SetInputType(tc.inputType) + input.SetQueue(tc.queueName) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create QoS output interface config") + schedulerIntfs := []struct { + desc string + queueName string + scheduler string + }{{ + desc: "output-interface-BE1", + queueName: queues.BE1, + scheduler: "scheduler", + }, { + desc: "output-interface-AF1", + queueName: queues.AF1, + scheduler: "scheduler", + }, { + desc: "output-interface-AF2", + queueName: queues.AF2, + scheduler: "scheduler", + }, { + desc: "output-interface-AF3", + queueName: queues.AF3, + scheduler: "scheduler", + }, { + desc: "output-interface-AF4", + queueName: queues.AF4, + scheduler: "scheduler", + }, { + desc: "output-interface-NC1", + queueName: queues.NC1, + scheduler: "scheduler", + }} + + t.Logf("QoS output interface config: %v", schedulerIntfs) + for _, tc := range schedulerIntfs { + i := q.GetOrCreateInterface(dp3.Name()) + i.SetInterfaceId(dp3.Name()) + i.GetOrCreateInterfaceRef().Interface = ygot.String(dp3.Name()) + if deviations.InterfaceRefConfigUnsupported(dut) { + i.InterfaceRef = nil + } + output := i.GetOrCreateOutput() + schedulerPolicy := output.GetOrCreateSchedulerPolicy() + schedulerPolicy.SetName(tc.scheduler) + queue := output.GetOrCreateQueue(tc.queueName) + queue.SetName(tc.queueName) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } +} + +func ConfigureDUTQoSIPv6(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + if deviations.QOSQueueRequiresID(dut) { + queueNames := []string{queues.NC1, queues.AF4, queues.AF3, queues.AF2, queues.AF1, queues.BE1} + for i, queue := range queueNames { + q1 := q.GetOrCreateQueue(queue) + q1.Name = ygot.String(queue) + queueid := len(queueNames) - i + q1.QueueId = ygot.Uint8(uint8(queueid)) + } + t.Logf("\nDUT %s %s %s requires QoS queue requires ID deviation \n\n", dut.Vendor(), dut.Model(), dut.Version()) + } + + t.Logf("Create QoS forwarding groups and queue names configuration") + forwardingGroups := []struct { + desc string + queueName string + targetGroup string + priority uint8 + }{{ + desc: "forwarding-group-BE1", + queueName: queues.BE1, + targetGroup: "target-group-BE1", + priority: 0, + }, { + desc: "forwarding-group-AF1", + queueName: queues.AF1, + targetGroup: "target-group-AF1", + priority: 1, + }, { + desc: "forwarding-group-AF2", + queueName: queues.AF2, + targetGroup: "target-group-AF2", + priority: 2, + }, { + desc: "forwarding-group-AF3", + queueName: queues.AF3, + targetGroup: "target-group-AF3", + priority: 3, + }, { + desc: "forwarding-group-AF4", + queueName: queues.AF4, + targetGroup: "target-group-AF4", + priority: 4, + }, { + desc: "forwarding-group-NC1", + queueName: queues.NC1, + targetGroup: "target-group-NC1", + priority: 5, + }} + + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + for _, tc := range forwardingGroups { + qoscfg.SetForwardingGroup(t, dut, q, tc.targetGroup, tc.queueName) + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + } + + t.Logf("Create QoS Classifiers config") + classifiers := []struct { + desc string + name string + classType oc.E_Qos_Classifier_Type + termID string + targetGroup string + dscpSet []uint8 + }{{ + desc: "classifier_ipv6_be1", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "0", + targetGroup: "target-group-BE1", + dscpSet: []uint8{0, 1, 2, 3, 4, 5, 6, 7}, + }, { + desc: "classifier_ipv6_af1", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "1", + targetGroup: "target-group-AF1", + dscpSet: []uint8{8, 9, 10, 11, 12, 13, 14, 15}, + }, { + desc: "classifier_ipv6_af2", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "2", + targetGroup: "target-group-AF2", + dscpSet: []uint8{16, 17, 18, 19, 20, 21, 22, 23}, + }, { + desc: "classifier_ipv6_af3", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "3", + targetGroup: "target-group-AF3", + dscpSet: []uint8{24, 25, 26, 27, 28, 29, 30, 31}, + }, { + desc: "classifier_ipv6_af4", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "4", + targetGroup: "target-group-AF4", + dscpSet: []uint8{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}, + }, { + desc: "classifier_ipv6_nc1", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "5", + targetGroup: "target-group-NC1", + dscpSet: []uint8{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}, + }} + + t.Logf("QoS classifiers config: %v", classifiers) + for _, tc := range classifiers { + classifier := q.GetOrCreateClassifier(tc.name) + classifier.SetName(tc.name) + classifier.SetType(tc.classType) + term, err := classifier.NewTerm(tc.termID) + if err != nil { + t.Fatalf("Failed to create classifier.NewTerm(): %v", err) + } + + term.SetId(tc.termID) + action := term.GetOrCreateActions() + action.SetTargetGroup(tc.targetGroup) + condition := term.GetOrCreateConditions() + condition.GetOrCreateIpv6().SetDscpSet(tc.dscpSet) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create QoS input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type IPV6", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV6, + classifier: "dscp_based_classifier_ipv6", + }, { + desc: "Input Classifier Type IPV6", + intf: dp2.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV6, + classifier: "dscp_based_classifier_ipv6", + }} + + t.Logf("QoS input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + + t.Logf("Create QoS scheduler policies config") + schedulerPolicies := []struct { + desc string + sequence uint32 + setPriority bool + priority oc.E_Scheduler_Priority + inputID string + inputType oc.E_Input_InputType + setWeight bool + queueName string + targetGroup string + }{{ + desc: "scheduler-policy-BE1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "BE1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.BE1, + targetGroup: "target-group-BE1", + }, { + desc: "scheduler-policy-AF1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF1, + targetGroup: "target-group-AF1", + }, { + desc: "scheduler-policy-AF2", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF2", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF2, + targetGroup: "target-group-AF2", + }, { + desc: "scheduler-policy-AF3", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF3", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF3, + targetGroup: "target-group-AF3", + }, { + desc: "scheduler-policy-AF4", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF4", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF4, + targetGroup: "target-group-AF4", + }, { + desc: "scheduler-policy-NC1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "NC1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.NC1, + targetGroup: "target-group-NC1", + }} + + schedulerPolicy := q.GetOrCreateSchedulerPolicy("scheduler") + schedulerPolicy.SetName("scheduler") + t.Logf("QoS scheduler policies config: %v", schedulerPolicies) + for _, tc := range schedulerPolicies { + s := schedulerPolicy.GetOrCreateScheduler(tc.sequence) + s.SetSequence(tc.sequence) + if tc.setPriority { + s.SetPriority(tc.priority) + } + input := s.GetOrCreateInput(tc.inputID) + input.SetId(tc.inputID) + input.SetInputType(tc.inputType) + input.SetQueue(tc.queueName) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create QoS output interface config") + schedulerIntfs := []struct { + desc string + queueName string + scheduler string + }{{ + desc: "output-interface-BE1", + queueName: queues.BE1, + scheduler: "scheduler", + }, { + desc: "output-interface-AF1", + queueName: queues.AF1, + scheduler: "scheduler", + }, { + desc: "output-interface-AF2", + queueName: queues.AF2, + scheduler: "scheduler", + }, { + desc: "output-interface-AF3", + queueName: queues.AF3, + scheduler: "scheduler", + }, { + desc: "output-interface-AF4", + queueName: queues.AF4, + scheduler: "scheduler", + }, { + desc: "output-interface-NC1", + queueName: queues.NC1, + scheduler: "scheduler", + }} + + t.Logf("QoS output interface config: %v", schedulerIntfs) + for _, tc := range schedulerIntfs { + i := q.GetOrCreateInterface(dp3.Name()) + i.SetInterfaceId(dp3.Name()) + i.GetOrCreateInterfaceRef().Interface = ygot.String(dp3.Name()) + if deviations.InterfaceRefConfigUnsupported(dut) { + i.InterfaceRef = nil + } + output := i.GetOrCreateOutput() + schedulerPolicy := output.GetOrCreateSchedulerPolicy() + schedulerPolicy.SetName(tc.scheduler) + queue := output.GetOrCreateQueue(tc.queueName) + queue.SetName(tc.queueName) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } +} + +func ConfigureDUTQoSMPLS(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + dp3 := dut.Port(t, "port3") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + if deviations.QOSQueueRequiresID(dut) { + queueNames := []string{queues.NC1, queues.AF4, queues.AF3, queues.AF2, queues.AF1, queues.BE1} + for i, queue := range queueNames { + q1 := q.GetOrCreateQueue(queue) + q1.Name = ygot.String(queue) + queueid := len(queueNames) - i + q1.QueueId = ygot.Uint8(uint8(queueid)) + } + t.Logf("\nDUT %s %s %s requires QoS queue requires ID deviation \n\n", dut.Vendor(), dut.Model(), dut.Version()) + } + + t.Logf("Create QoS forwarding groups and queue names configuration") + forwardingGroups := []struct { + desc string + queueName string + targetGroup string + priority uint8 + }{{ + desc: "forwarding-group-BE1", + queueName: queues.BE1, + targetGroup: "target-group-BE1", + priority: 0, + }, { + desc: "forwarding-group-AF1", + queueName: queues.AF1, + targetGroup: "target-group-AF1", + priority: 1, + }, { + desc: "forwarding-group-AF2", + queueName: queues.AF2, + targetGroup: "target-group-AF2", + priority: 2, + }, { + desc: "forwarding-group-AF3", + queueName: queues.AF3, + targetGroup: "target-group-AF3", + priority: 3, + }, { + desc: "forwarding-group-AF4", + queueName: queues.AF4, + targetGroup: "target-group-AF4", + priority: 4, + }, { + desc: "forwarding-group-NC1", + queueName: queues.NC1, + targetGroup: "target-group-NC1", + priority: 5, + }} + + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + for _, tc := range forwardingGroups { + qoscfg.SetForwardingGroup(t, dut, q, tc.targetGroup, tc.queueName) + + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + } + + t.Logf("Create QoS Classifiers config") + classifiers := []struct { + desc string + name string + classType oc.E_Qos_Classifier_Type + termID string + targetGroup string + dscpSet []uint8 + }{{ + desc: "classifier_mpls_be1", + name: "dscp_based_classifier_mpls", + classType: oc.Qos_Classifier_Type_MPLS, + termID: "0", + targetGroup: "target-group-BE1", + dscpSet: []uint8{0}, + }, { + desc: "classifier_mpls_af1", + name: "dscp_based_classifier_mpls", + classType: oc.Qos_Classifier_Type_MPLS, + termID: "1", + targetGroup: "target-group-AF1", + dscpSet: []uint8{1}, + }, { + desc: "classifier_mpls_af2", + name: "dscp_based_classifier_mpls", + classType: oc.Qos_Classifier_Type_MPLS, + termID: "2", + targetGroup: "target-group-AF2", + dscpSet: []uint8{2}, + }, { + desc: "classifier_mpls_af3", + name: "dscp_based_classifier_mpls", + classType: oc.Qos_Classifier_Type_MPLS, + termID: "3", + targetGroup: "target-group-AF3", + dscpSet: []uint8{3}, + }, { + desc: "classifier_mpls_af4", + name: "dscp_based_classifier_mpls", + classType: oc.Qos_Classifier_Type_MPLS, + termID: "4", + targetGroup: "target-group-AF4", + dscpSet: []uint8{4, 5}, + }, { + desc: "classifier_mpls_nc1", + name: "dscp_based_classifier_mpls", + classType: oc.Qos_Classifier_Type_MPLS, + termID: "5", + targetGroup: "target-group-NC1", + dscpSet: []uint8{6, 7}, + }} + + t.Logf("QoS classifiers config: %v", classifiers) + if deviations.MplsExpIngressClassifierOcUnsupported(dut) { + configureMplsExpClassifierCLI(t, dut, classifiers) + } else { + for _, tc := range classifiers { + classifier := q.GetOrCreateClassifier(tc.name) + classifier.SetName(tc.name) + classifier.SetType(tc.classType) + + term, err := classifier.NewTerm(tc.termID) + if err != nil { + t.Fatalf("Failed to create classifier.NewTerm(): %v", err) + } + + term.SetId(tc.termID) + action := term.GetOrCreateActions() + action.SetTargetGroup(tc.targetGroup) + condition := term.GetOrCreateConditions() + condition.GetOrCreateMpls().SetTrafficClass(tc.dscpSet[0]) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create QoS input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type MPLS", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_MPLS, + classifier: "dscp_based_classifier_mpls", + }, { + desc: "Input Classifier Type MPLS", + intf: dp2.Name(), + inputClassifierType: oc.Input_Classifier_Type_MPLS, + classifier: "dscp_based_classifier_mpls", + }} + + t.Logf("QoS input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + } + + t.Logf("Create QoS scheduler policies config") + schedulerPolicies := []struct { + desc string + sequence uint32 + setPriority bool + priority oc.E_Scheduler_Priority + inputID string + inputType oc.E_Input_InputType + setWeight bool + queueName string + targetGroup string + }{{ + desc: "scheduler-policy-BE1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "BE1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.BE1, + targetGroup: "target-group-BE1", + }, { + desc: "scheduler-policy-AF1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF1, + targetGroup: "target-group-AF1", + }, { + desc: "scheduler-policy-AF2", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF2", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF2, + targetGroup: "target-group-AF2", + }, { + desc: "scheduler-policy-AF3", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF3", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF3, + targetGroup: "target-group-AF3", + }, { + desc: "scheduler-policy-AF4", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "AF4", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.AF4, + targetGroup: "target-group-AF4", + }, { + desc: "scheduler-policy-NC1", + sequence: uint32(0), + setPriority: true, + setWeight: false, + priority: oc.Scheduler_Priority_STRICT, + inputID: "NC1", + inputType: oc.Input_InputType_QUEUE, + queueName: queues.NC1, + targetGroup: "target-group-NC1", + }} + + schedulerPolicy := q.GetOrCreateSchedulerPolicy("scheduler") + schedulerPolicy.SetName("scheduler") + t.Logf("QoS scheduler policies config: %v", schedulerPolicies) + for _, tc := range schedulerPolicies { + s := schedulerPolicy.GetOrCreateScheduler(tc.sequence) + s.SetSequence(tc.sequence) + if tc.setPriority { + s.SetPriority(tc.priority) + } + input := s.GetOrCreateInput(tc.inputID) + input.SetId(tc.inputID) + input.SetInputType(tc.inputType) + input.SetQueue(tc.queueName) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create QoS output interface config") + schedulerIntfs := []struct { + desc string + queueName string + scheduler string + }{{ + desc: "output-interface-BE1", + queueName: queues.BE1, + scheduler: "scheduler", + }, { + desc: "output-interface-AF1", + queueName: queues.AF1, + scheduler: "scheduler", + }, { + desc: "output-interface-AF2", + queueName: queues.AF2, + scheduler: "scheduler", + }, { + desc: "output-interface-AF3", + queueName: queues.AF3, + scheduler: "scheduler", + }, { + desc: "output-interface-AF4", + queueName: queues.AF4, + scheduler: "scheduler", + }, { + desc: "output-interface-NC1", + queueName: queues.NC1, + scheduler: "scheduler", + }} + + t.Logf("QoS output interface config: %v", schedulerIntfs) + for _, tc := range schedulerIntfs { + i := q.GetOrCreateInterface(dp3.Name()) + i.SetInterfaceId(dp3.Name()) + i.GetOrCreateInterfaceRef().Interface = ygot.String(dp3.Name()) + if deviations.InterfaceRefConfigUnsupported(dut) { + i.InterfaceRef = nil + } + output := i.GetOrCreateOutput() + schedulerPolicy := output.GetOrCreateSchedulerPolicy() + schedulerPolicy.SetName(tc.scheduler) + queue := output.GetOrCreateQueue(tc.queueName) + queue.SetName(tc.queueName) + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } +} + +func runCliCommand(t *testing.T, dut *ondatra.DUTDevice, cliCommand string) string { + cliClient := dut.RawAPIs().CLI(t) + output, err := cliClient.RunCommand(context.Background(), cliCommand) + if err != nil { + t.Fatalf("Failed to execute CLI command '%s': %v", cliCommand, err) + } + t.Logf("Received from cli: %s", output.Output()) + return output.Output() +} + +func configureQoSGlobalParams(t *testing.T, dut *ondatra.DUTDevice) { + + queues := netutil.CommonTrafficQueues(t, dut) + qosQNameSet := ` + configure terminal + ! + qos tx-queue %d name %s + ! + ` + qosMapTC := ` + configure terminal + ! + qos map traffic-class %d to tx-queue %d + ! + ` + + qosCfgTargetGroup := ` + configure terminal + ! + qos traffic-class %d name %s + ! + ` + + runCliCommand(t, dut, "show version") + + qList := []string{queues.BE1, queues.AF1, queues.AF2, queues.AF3, queues.AF4, queues.NC1} + for index, queue := range qList { + + runCliCommand(t, dut, fmt.Sprintf(qosQNameSet, index, queue)) + time.Sleep(time.Second) + runCliCommand(t, dut, fmt.Sprintf(qosMapTC, index, index)) + time.Sleep(time.Second) + runCliCommand(t, dut, fmt.Sprintf(qosCfgTargetGroup, index, fmt.Sprintf("target-group-%s", queue))) + time.Sleep(time.Second) + } + +} + +func buildCliSetRequest(config string) *gpb.SetRequest { + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Origin: "cli", + Elem: []*gpb.PathElem{}, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_AsciiVal{ + AsciiVal: config, + }, + }, + }, + }, + } + return gpbSetRequest +} + +func configureMplsExpClassifierCLI(t *testing.T, dut *ondatra.DUTDevice, classifiers []struct { + desc string + name string + classType oc.E_Qos_Classifier_Type + termID string + targetGroup string + dscpSet []uint8 +}) { + + qosMapCmd := fmt.Sprintf(` + mpls ip + ! + mpls static top-label %d %s pop payload-type ipv4 + ! + `, mplsLabel, ateRxP3.IPv4) + qosMapExp := ` + qos map exp %d to traffic-class %s + ! + ` + + for _, classifier := range classifiers { + tc := classifier.termID + for _, exp := range classifier.dscpSet { + qosMapCmd += fmt.Sprintf(qosMapExp, exp, tc) + t.Logf(qosMapExp, exp, tc) + + } + } + + gnmiClient := dut.RawAPIs().GNMI(t) + t.Logf("Push the CLI QoS config:%s", dut.Vendor()) + gpbSetRequest := buildCliSetRequest(qosMapCmd) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Failed to set QoS Exp mappings from CLI: %v", err) + } +} + +func configureHardwareInit(t *testing.T, dut *ondatra.DUTDevice) { + hardwareInitCfg := cfgplugins.NewDUTHardwareInit(t, dut, cfgplugins.FeatureMplsTracking) + if hardwareInitCfg == "" { + return + } + cfgplugins.PushDUTHardwareInitConfig(t, dut, hardwareInitCfg) +} + +func waitForTraffic(t *testing.T, otg *otg.OTG, flowName string, timeout time.Duration) { + transmitPath := gnmi.OTG().Flow(flowName).Transmit().State() + _, ok := gnmi.Watch(t, otg, transmitPath, timeout, func(val *ygnmi.Value[bool]) bool { + transmitState, present := val.Val() + return present && !transmitState + }).Await(t) + + if !ok { + t.Errorf("Traffic for flow %s did not stop within the timeout of %d", flowName, timeout) + } else { + t.Logf("Traffic for flow %s has stopped", flowName) + } +} diff --git a/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/metadata.textproto b/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/metadata.textproto new file mode 100644 index 00000000000..fee0645c661 --- /dev/null +++ b/feature/qos/otg_tests/egress_strict_priority_scheduler_with_bursty_traffic_test/metadata.textproto @@ -0,0 +1,47 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1fb29b52-8603-4b35-a963-ab6295ee4693" +plan_id: "DP-1.5" +description: "Egress Strict Priority scheduler with bursty traffic" +testbed: TESTBED_DUT_ATE_4LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: JUNIPER + } + deviations: { + ecn_profile_required_definition: true + no_zero_suppression: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + explicit_port_speed: true + explicit_interface_in_default_vrf: true + qos_octets: true + qos_queue_requires_id: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + unsupported_qos_output_service_policy: true + interface_output_queue_non_standard_name: true + mpls_exp_ingress_classifier_oc_unsupported: true + } +} diff --git a/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/README.md b/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/README.md new file mode 100644 index 00000000000..91dbdf9e1b7 --- /dev/null +++ b/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/README.md @@ -0,0 +1,232 @@ +# DP-1.19: Egress traffic DSCP rewrite + +## Summary + +This test validates egress traffic scheduling and packet remarking (rewrite) on a DUT. IP payload encapsulated as IPoGRE, +IPoMPLSoGRE,IPoGUE or IPoMPLSoGUE would be decapsulated by DUT as per tunnel termination configuration and forwarded as an +IP payload via the egress interface. IP payload should be scheduled via egress interface according to DSCP markings prior to rewrite. +DSCP values of IP payload encapsulated as IPoGRE, IPoMPLSoGRE, IPoGUE or IPoMPLSoGUE should have payload DSCP values re-written +per egress QOS re-write policy applied to the egress interface. Egress QOS policy should support match conditions on payload +IP header fields, specifically DSCP values. Based on the match condition, DSCP must be set to a new value. DUT configuration +will be evaluated against OpenConfig QOS model, and traffic flows analyzed to ensure proper scheduling, re-marking, and forwarding. + +## Testbed type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + +* DUT has an ingress port and 1 egress port. + +| | [ ATE Port 1 ] ----- | DUT | ----- [ ATE Port 2 ] | | + + +* Configure DUT's ingress and egress interfaces. + + +#### Configuration + +It is assumed that DUT supports 8 QOS queues and use the following class mapping to code points: +| FWD | Code-point | DSCP | TOS | Queue | +| class | Bits | value | value | | +| ————--| ---------- | ——————— | ————— | ———--- | +| NC1 | 1110xx | 56 - 59 | E0-EF | NC1 | +| ————--| ---------- | ——————— | ————— | ———--- | +| NC1 | 1100xx | 48 - 51 | C0-CF | NC1 | +| ————--| ---------- | ——————— | ————— | ———--- | +| AF4 | 1000xx | 32 - 35 | 80-8F | AF4 | +| ————--| ---------- | ——————— | ————— | ———--- | +| AF3 | 0110xx | 24 - 27 | 60-6F | AF3 | +| ————--| ---------- | ——————— | ————— | ———--- | +| AF2 | 0100xx | 16 - 19 | 40-4F | AF2 | +| ————--| ---------- | ——————— | ————— | ———--- | +| AF1 | 0010xx | 8 - 11 | 20-2F | AF1 | +| ————--| ---------- | ——————— | ————— | ———--- | +| BE0 | 0001xx | 4 - 7 | 10-1F | BE1 | +| ————--| ---------- | ——————— | ————— | ———--- | +| BE1 | 0000xx | 0 - 3 | 00-0F | BE1 | + +1. DUT:Port1 is a Singleton IP interface towards ATE:Port1. +2. DUT:Port2 is a Singleton IP interface towards ATE:Port2. +3. DUT has egress forwarding entry in it's FIB via ATE:Port1 towards ATE:Port2: BGP routing could be used to populate FIB as follows: + 3a. DUT forms one IPv4 and one IPV6 eBGP session with ATE:Port1 using connected Singleton interface IPs. + 3b. DUT forms one IPv4 and one IPV6 eBGP session with ATE:Port2 using connected Singleton interface IPs. + 3c. DUT has IPv4-DST-DECAP/32 and IPv6-DST-DECAP/128 advertised to ATE:Port1 via IPv4 BGP. This IP is used for decapsulation. +7. ATE:Port2 advertises destination networks IPv4-DST-NET/32 and IPv6-DST-NET/128 to DUT. +8. DUT decapsulates IPoGRE, IPoMPLSoGRE,IPoGUE or IPoMPLSoGUE payload with destination of IPv4-DST-DECAP/32 and IPv6-DST-DECAP/128 +9. DUT has MPLS static forwarding rule (aka static LSP) for label 100020 pointing to a NHG resolved via ATE:Port2. +10. DUT matches packets on DSCP/TC values and sets new DSCP values based on remarking rules. + + +* Re-marking rules: + +| Queue | TOS | TOS | +| | v4 | v6 | +| ----- | --- | --- | +| BE1 | 0 | 0 | +| ----- | --- | --- | +| AF1 | 0 | 0 | +| ----- | --- | --- | +| AF2 | 0 | 0 | +| ----- | --- | --- | +| AF3 | 0 | 0 | +| ----- | --- | --- | +| AF4 | 0 | 0 | +| ----- | --- | --- | +| NC1 | 6 | 48 | + + +### DP-1.17.1 Egress Classification and rewrite of IPv4 packets with various DSCP values + +* Traffic: + * Generate IPv4 traffic from ATE Port 1 with various DSCP values per table listed in the "Test environment setup" section. +* Verification: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues by observing queue counters per configued QOS classes. + * Capture packets on ATE Port 2 ingress to verify packet re-marking per table above. + * Analyze traffic flows to confirm no packet drops on DUT. + +### DP-1.17.2 Egress Classification and rewrite of IPv6 packets with various TC values + +* Traffic: + * Generate IPv6 traffic from ATE Port 1 with various DSCP values per table listed in the "Test environment setup" section. +* Verification: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes. + * Capture packets on ATE Port 2 ingress to verify packet marking per table above. + * Analyze traffic flows, confirming no drops on DUT. + +### DP-1.17.3 Egress Classification and rewrite of IPoMPLSoGUE traffic with pop action + +* Configuration: + * Configure Static MPLS LSP with MPLS pop and IPv4/IPv6 forward actions for a specific label 100020 via ATE Port 1. + * Configure decapsulation rules for IPv4-DST-DECAP/32 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. +* Traffic: + * Generate IPoMPLSoGUE traffic from ATE Port 1 with label 100020 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes.. + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.4 Egress Classification and rewrite of IPv6oMPLSoGUE traffic with pop action +* Configuration: + * Configure Static MPLS LSP with MPLS pop and IPv6 forward actions for a specific label 10020. + * Configure decapsulation rules for IPv6-DST-DECAP/12 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. + +* Traffic: + * Generate IPv6oMPLSoGUE traffic from ATE Port 1 with label 100020 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes. + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.5 Egress Classification and rewrite of IPoMPLSoGRE traffic with pop action +* Configuration: + * Configure Static MPLS LSP with MPLS pop and IPv4 forward actions for a specific label 100020 + * Configure decapsulation rules for IPv4-DST-DECAP/32 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. + +* Traffic: + * Generate IPoMPLSoGRE traffic from ATE Port 1 with label 100020 + * Verfication: + * Monitor telemetry on the DUT to verify that packets re being scehduled for transmission into correct forwarding groups based on the DSCP markings of the payload prir to re-write rules + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.7 Egress Classification and rewrite of IPv6oMPLSoGRE traffic with pop action +* Configuration: + * Configure Static MPLS LSP with MPLS pop and IPv6 forward actions for a specific label 100020. + * Configure decapsulation rules for IPv6-DST-DECAP/12 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. + +* Traffic: + * Generate IPv6oMPLSoGRE traffic from ATE Port 1 with label 100020 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes. + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.8 Egress Classification and rewrite of IPoGRE traffic with decapsulate action +* Configuration: + * Configure decapsulation rules for IPv4-DST-DECAP/32 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. + +* Traffic: + * Generate IPoGRE traffic from ATE Port 1 with IP payload dest reachable via ATE:Port2 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes.. + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.9 Egress Classification and rewrite of IPv6oGRE traffic with decapsulate action +* Configuration: + * Configure decapsulation rules for IPv6-DST-DECAP/12 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. +* Traffic: + * Generate IPv6oGRE traffic from ATE Port 1 with payload reachable via ATE:Port2 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes.. + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.10 Egress Classification and rewrite of IPoGUE traffic with pop action +* Configuration:ƒ + * Configure decapsulation rules for IPv4-DST-DECAP/32 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. + +* Traffic: + * Generate IPoGUE traffic from ATE Port 1 with payload reachable via ATE:Port2 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes.. ƒ + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.17.11 Egress Classification and rewrite of IPv6oGUE traffic with pop action +* Configuration: + * Configure decapsulation rules for Id IPv6-DST-DECAP/12 + * Configufe egress QOS re-write rules based on DSCP/TC values and set new DSCP values based on remarking rules. + +* Traffic: + * Generate IPv6oGUE traffic from ATE Port 1 with IPv6 payload reachable via ATE:Port2 +* Verfication: + * Monitor telemetry on DUT ATE Port 1 to verify packet scheduling into correct egress QOS queues based on the DSCP markings of the payload prir to re-write rules by observing queue counters per configued QOS classes. + * Capture packets on the DUT ATE Port 2 ingress interface to verify packet marking according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + + +## OpenConfig Path and RPC Coverage + +The below yaml defines the OC paths intended to be covered by this test. + +```yaml +paths: + ## Config paths + /qos/classifiers/classifier/config/name: + /qos/classifiers/classifier/config/type: + /qos/classifiers/classifier/terms/term/config/id: + /qos/classifiers/classifier/terms/term/actions/config/target-group: + /qos/classifiers/classifier/terms/term/conditions/ipv4/config/dscp: + /qos/classifiers/classifier/terms/term/conditions/ipv4/config/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/ipv6/config/dscp: + /qos/classifiers/classifier/terms/term/conditions/ipv6/config/dscp-set: + /qos/classifiers/classifier/terms/term/conditions/mpls/config/traffic-class: + /qos/classifiers/classifier/terms/term/actions/remark/config/set-dscp: + /qos/classifiers/classifier/terms/term/actions/remark/config/set-mpls-tc: + /qos/interfaces/interface/input/classifiers/classifier/config/name: + /qos/interfaces/interface/input/classifiers/classifier/config/type: + + ## State paths + /qos/interfaces/interface/input/classifiers/classifier/terms/term/state/matched-packets: + /qos/interfaces/interface/input/classifiers/classifier/terms/term/state/matched-octets: + +rpcs: + gnmi: + gNMI.Set: + gNMI.Subscribe: +``` + +## Minimum DUT platform requirement + +* FFF - fixed form factor diff --git a/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/metadata.textproto b/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/metadata.textproto new file mode 100644 index 00000000000..d4f09935676 --- /dev/null +++ b/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "cf3f42af-1463-4a3b-b691-bf06edc3db14" +plan_id: "DP-1.19" +description: "Egress traffic DSCP rewrite" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/README.md b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/README.md index 93feae4293b..ba5d4a41aa1 100644 --- a/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/README.md +++ b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/README.md @@ -117,6 +117,138 @@ This test aims to validate the functionality of ingress traffic classification a * Capture packets on the ATE's ingress interface to verify packet marking according to the marking table. * Analyze traffic flows to confirm that no packets are dropped on the DUT. +### DP-1.16.8 Ingress Classification and rewrite of IPV4 traffic with action GRE encap + +* Configuration: + * Configure GRE to encap traffic from the ATE port1 + * GRE packets should set the DSCP similar to the native IPV4 packets +* Traffic: + * Generate IPV4 traffic from ATE Port 1 + * DUT should encap the IPV4 traffic in GRE destined to ATE port2 +* Verfication: + * Monitor telemetry on the DUT to verify that packets are being matched to the correct classifier terms. + * Capture packets on the ATE's ingress interface to verify packet marking according to the marking table. + * Capture packets on the ATE's egress interface to verify the GRE encapped packet marking is also according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.16.9 Ingress Classification and rewrite of IPV6 traffic with action GRE encap + +* Configuration: + * Configure GRE to encap traffic from the ATE port1 + * GRE packets should set the DSCP similar to the native IPV6 packets +* Traffic: + * Generate IPV6 traffic from ATE Port 1 + * DUT should encap the IPV6 traffic in GRE destined to ATE port2 +* Verfication: + * Monitor telemetry on the DUT to verify that packets are being matched to the correct classifier terms. + * Capture packets on the ATE's ingress interface to verify packet marking according to the marking table. + * Capture packets on the ATE's egress interface to verify the GRE encapped packet marking is also according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.16.10 Ingress Classification and rewrite of IPV4 traffic with action GUE variant1 encap + +* Configuration: + * Configure GUE Variant1 to encap traffic from the ATE port1 + * GUE variant1 packets should set the DSCP similar to the native IPV4 packets +* Traffic: + * Generate IPV4 traffic from ATE Port 1 + * DUT should encap the IPV4 traffic in GUE destined to ATE port2 +* Verfication: + * Monitor telemetry on the DUT to verify that packets are being matched to the correct classifier terms. + * Capture packets on the ATE's ingress interface to verify packet marking according to the marking table. + * Capture packets on the ATE's egress interface to verify teh GUE encapped packet marking is also according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +### DP-1.16.11 Ingress Classification and rewrite of IPV6 traffic with action GUE variant1 encap + +* Configuration: + * Configure GUE Variant1 to encap traffic from the ATE port1 + * GUE variant1 packets should set the DSCP similar to the native IPV6 packets +* Traffic: + * Generate IPV6 traffic from ATE Port 1 + * DUT should encap the IPV6 traffic in GUE destined to ATE port2 +* Verfication: + * Monitor telemetry on the DUT to verify that packets are being matched to the correct classifier terms. + * Capture packets on the ATE's ingress interface to verify packet marking according to the marking table. + * Capture packets on the ATE's egress interface to verify the GRE encapped packet marking is also according to the marking table. + * Analyze traffic flows to confirm that no packets are dropped on the DUT. + +```json +# qos classifier config + +{ + "qos": { + "classifiers": { + "classifier": { + "config": { + "name": , + "type": + }, + "terms": { + "term": { + "config": { + "id": + }, + "actions": { + "config": { + "target-group": + }, + "remark": { + "config": { + "set-dscp": , + "set-mpls-tc": + } + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp": , + "dscp-set": + } + }, + "ipv6": { + "config": { + "dscp": , + "dscp-set": + } + }, + "mpls": { + "config": { + "traffic-class": + } + } + } + } + } + } + } + } +} + +# Applying classfiers on the interface + +{ + "qos": { + "interfaces": { + "interface": { + "input": { + "classifiers": { + "classifier": { + "config": { + "name": , + "type": + } + } + } + } + } + } + } +} + +``` + ## OpenConfig Path and RPC Coverage The below yaml defines the OC paths intended to be covered by this test. diff --git a/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/ingress_traffic_classification_and_rewrite_test.go b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/ingress_traffic_classification_and_rewrite_test.go new file mode 100644 index 00000000000..8f2136cf0df --- /dev/null +++ b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/ingress_traffic_classification_and_rewrite_test.go @@ -0,0 +1,1461 @@ +// 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 ingress_traffic_classification_and_rewrite_test + +import ( + "fmt" + "net" + "os" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/featureprofiles/internal/qoscfg" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygnmi/ygnmi" + "github.com/openconfig/ygot/ygot" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + mplsSwapLabel = 24000 + mplsSwapLabelTo = 25000 + mplsPopLabelv4 = 26000 + mplsPopLabelv6 = 27000 + mplsPushLabelV4 = 28000 + mplsPushLabelV6 = 29000 + ipv4DestAddr = "203.1.1.1" + ipv4DestAddrWithCidr = "203.1.1.1/32" + ipv6DestAddr = "203:0:0:1::1" + ipv6DestAddrWithCidr = "203:0:0:1::1/128" + frameSize = 512 + packetPerSecond = 100 + maxIpv6Tos = 63 + timeout = 30 + trafficSleepTime = 10 + captureWait = 10 + lspNextHopIndex = 0 + implicitNull = 3 + trafficPolicyName = "GRE_GUE_MATCH_TRAFFIC_POLICY" + executeGre = true + donotExecuteGre = false + executeGue = true + donotExecuteGue = false + greProtocol = 47 + gueProtocolPort = 6080 +) + +var ( + dutlo0Attrs = attrs.Attributes{ + Name: "Loopback0", + IPv4: "192.0.20.2", + IPv6: "2001:DB8:0::10", + IPv4Len: 32, + IPv6Len: 128, + } + + atePort1 = attrs.Attributes{ + Name: "ateP1", + MAC: "02:00:01:01:01:01", + IPv4: "192.0.2.2", + IPv6: "2001:db8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort2 = attrs.Attributes{ + Name: "ateP2", + MAC: "02:00:02:01:01:01", + IPv4: "192.0.2.6", + IPv6: "2001:db8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutPort1 = &attrs.Attributes{ + Desc: "dutPort1", + IPv6: "2001:db8::1", + IPv4: "192.0.2.1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + dutPort2 = &attrs.Attributes{ + Desc: "dutPort2", + IPv6: "2001:db8::5", + IPv4: "192.0.2.5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestIngressTrafficClassificationAndRewrite(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + dp1 := dut.Port(t, "port1") + dp2 := dut.Port(t, "port2") + t.Log(dp1, dp2) + + // Configure DUT interfaces. + ConfigureDUTIntf(t, dut) + ConfigureQoS(t, dut) + + // Configure Loopback interface + configureLoopbackInterface(t, dut) + + // configure static routes + configureStaticRoutes(t, dut) + + // configure ATE + topo := configureATE(t) + ate.OTG().PushConfig(t, topo) + enableCapture(t, topo, "port2") + + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), topo, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), topo, "IPv6") + + t.Run("DP-1.16.1 Ingress Classification and rewrite of IPv4 packets with various DSCP values", func(t *testing.T) { + rewriteIpv4PktsWithDscp(t, dut, ate, topo, donotExecuteGre, donotExecuteGue) + }) + t.Run("DP-1.16.2 Ingress Classification and rewrite of IPv6 packets with various DSCP values", func(t *testing.T) { + rewriteIpv6PktsWithDscp(t, dut, ate, topo, donotExecuteGre, donotExecuteGue) + }) + t.Run("DP-1.16.3 Ingress Classification and rewrite of MPLS traffic with swap action", func(t *testing.T) { + rewriteMplsSwapAction(t, dut, ate, topo) + }) + t.Run("DP-1.16.4 Ingress Classification and rewrite of IPv4-over-MPLS traffic with pop action", func(t *testing.T) { + rewriteIpv4MplsPopAction(t, dut, ate, topo) + }) + t.Run("DP-1.16.5 Ingress Classification and rewrite of IPv6-over-MPLS traffic with pop action", func(t *testing.T) { + rewriteIpv6MplsPopAction(t, dut, ate, topo) + }) + t.Run("DP-1.16.6 Ingress Classification and rewrite of IPv4 packets traffic with label push action", func(t *testing.T) { + rewriteIpv4MplsPushAction(t, dut, ate, topo) + }) + t.Run("DP-1.16.7 Ingress Classification and rewrite of IPv6 packets traffic with label push action", func(t *testing.T) { + rewriteIpv6MplsPushAction(t, dut, ate, topo) + }) + t.Run("DP-1.16.8 Ingress Classification and rewrite of IPV4 traffic with action GRE encap", func(t *testing.T) { + rewriteIpv4PktsWithDscp(t, dut, ate, topo, executeGre, donotExecuteGue) + }) + t.Run("DP-1.16.9 Ingress Classification and rewrite of IPV6 traffic with action GRE encap", func(t *testing.T) { + rewriteIpv6PktsWithDscp(t, dut, ate, topo, executeGre, donotExecuteGue) + }) + t.Run("DP-1.16.10 Ingress Classification and rewrite of IPV4 traffic with action GUE variant1 encap", func(t *testing.T) { + rewriteIpv4PktsWithDscp(t, dut, ate, topo, donotExecuteGre, executeGue) + }) + t.Run("DP-1.16.11 Ingress Classification and rewrite of IPV6 traffic with action GUE variant1 encap", func(t *testing.T) { + rewriteIpv6PktsWithDscp(t, dut, ate, topo, donotExecuteGre, executeGue) + }) +} + +func ConfigureDUTIntf(t *testing.T, dut *ondatra.DUTDevice) { + d := gnmi.OC() + p1 := dut.Port(t, "port1") + gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), configInterfaceDUT(p1, dutPort1, dut)) + p2 := dut.Port(t, "port2") + gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), configInterfaceDUT(p2, dutPort2, dut)) + + // Configure Network instance type on DUT + t.Log("Configure/update Network Instance") + fptest.ConfigureDefaultNetworkInstance(t, dut) +} + +// Configures the given DUT interface. +func configInterfaceDUT(p *ondatra.Port, a *attrs.Attributes, dut *ondatra.DUTDevice) *oc.Interface { + i := a.NewOCInterface(p.Name(), dut) + s4 := i.GetOrCreateSubinterface(0).GetOrCreateIpv4() + if deviations.InterfaceEnabled(dut) && !deviations.IPv4MissingEnabled(dut) { + s4.Enabled = ygot.Bool(true) + } + i.GetOrCreateSubinterface(0).GetOrCreateIpv6() + + return i +} + +func ConfigureQoS(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dp1 := dut.Port(t, "port1") + d := &oc.Root{} + q := d.GetOrCreateQos() + queues := netutil.CommonTrafficQueues(t, dut) + + if deviations.QOSQueueRequiresID(dut) { + queueNames := []string{queues.NC1, queues.AF4, queues.AF3, queues.AF2, queues.AF1, queues.BE1} + for i, queue := range queueNames { + q1 := q.GetOrCreateQueue(queue) + q1.Name = ygot.String(queue) + queueid := len(queueNames) - i + q1.QueueId = ygot.Uint8(uint8(queueid)) + } + } + t.Logf("Create qos forwarding groups config") + forwardingGroups := []struct { + desc string + queueName string + targetGroup string + }{{ + desc: "forwarding-group-BE1", + queueName: queues.BE1, + targetGroup: "target-group-BE1", + }, { + desc: "forwarding-group-AF1", + queueName: queues.AF1, + targetGroup: "target-group-AF1", + }, { + desc: "forwarding-group-AF2", + queueName: queues.AF2, + targetGroup: "target-group-AF2", + }, { + desc: "forwarding-group-AF3", + queueName: queues.AF3, + targetGroup: "target-group-AF3", + }, { + desc: "forwarding-group-AF4", + queueName: queues.AF4, + targetGroup: "target-group-AF4", + }, { + desc: "forwarding-group-NC1", + queueName: queues.NC1, + targetGroup: "target-group-NC1", + }} + + t.Logf("qos forwarding groups config: %v", forwardingGroups) + for _, tc := range forwardingGroups { + qoscfg.SetForwardingGroup(t, dut, q, tc.targetGroup, tc.queueName) + } + + t.Logf("Create qos Classifiers config") + classifiers := []struct { + desc string + name string + classType oc.E_Qos_Classifier_Type + termID string + targetGroup string + dscpSet []uint8 + expSet []uint8 // MPLS EXP values + }{{ + desc: "classifier_ipv4_be1", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "0", + targetGroup: "target-group-BE1", + dscpSet: []uint8{0}, + }, { + desc: "classifier_ipv4_af1", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "1", + targetGroup: "target-group-AF1", + dscpSet: []uint8{1}, + }, { + desc: "classifier_ipv4_af2", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "2", + targetGroup: "target-group-AF2", + dscpSet: []uint8{2}, + }, { + desc: "classifier_ipv4_af3", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "3", + targetGroup: "target-group-AF3", + dscpSet: []uint8{3}, + }, { + desc: "classifier_ipv4_af4", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "4", + targetGroup: "target-group-AF4", + dscpSet: []uint8{4, 5}, + }, { + desc: "classifier_ipv4_nc1", + name: "dscp_based_classifier_ipv4", + classType: oc.Qos_Classifier_Type_IPV4, + termID: "6", + targetGroup: "target-group-NC1", + dscpSet: []uint8{6, 7}, + }, { + desc: "classifier_ipv6_be1", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "0", + targetGroup: "target-group-BE1", + dscpSet: []uint8{0, 1, 2, 3, 4, 5, 6, 7}, + }, { + desc: "classifier_ipv6_af1", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "1", + targetGroup: "target-group-AF1", + dscpSet: []uint8{8, 9, 10, 11, 12, 13, 14, 15}, + }, { + desc: "classifier_ipv6_af2", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "2", + targetGroup: "target-group-AF2", + dscpSet: []uint8{16, 17, 18, 19, 20, 21, 22, 23}, + }, { + desc: "classifier_ipv6_af3", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "3", + targetGroup: "target-group-AF3", + dscpSet: []uint8{24, 25, 26, 27, 28, 29, 30, 31}, + }, { + desc: "classifier_ipv6_af4", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "4", + targetGroup: "target-group-AF4", + dscpSet: []uint8{32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47}, + }, { + desc: "classifier_ipv6_nc1", + name: "dscp_based_classifier_ipv6", + classType: oc.Qos_Classifier_Type_IPV6, + termID: "6", + targetGroup: "target-group-NC1", + dscpSet: []uint8{48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63}, + // }, { + // desc: "classifier_mpls_be1", + // name: "exp_based_classifier_mpls", + // classType: oc.Qos_Classifier_Type_MPLS, + // termID: "0", + // targetGroup: "target-group-BE1", + // expSet: []uint8{0}, + // }, { + // desc: "classifier_mpls_af1", + // name: "exp_based_classifier_mpls", + // classType: oc.Qos_Classifier_Type_MPLS, + // termID: "1", + // targetGroup: "target-group-AF1", + // expSet: []uint8{1}, + // }, { + // desc: "classifier_mpls_af2", + // name: "exp_based_classifier_mpls", + // classType: oc.Qos_Classifier_Type_MPLS, + // termID: "2", + // targetGroup: "target-group-AF2", + // expSet: []uint8{2}, + // }, { + // desc: "classifier_mpls_af3", + // name: "exp_based_classifier_mpls", + // classType: oc.Qos_Classifier_Type_MPLS, + // termID: "3", + // targetGroup: "target-group-AF3", + // expSet: []uint8{3}, + // }, { + // desc: "classifier_mpls_af4", + // name: "exp_based_classifier_mpls", + // classType: oc.Qos_Classifier_Type_MPLS, + // termID: "4", + // targetGroup: "target-group-AF4", + // expSet: []uint8{4, 5}, + // }, { + // desc: "classifier_mpls_nc1", + // name: "exp_based_classifier_mpls", + // classType: oc.Qos_Classifier_Type_MPLS, + // termID: "6", + // targetGroup: "target-group-NC1", + // expSet: []uint8{6, 7}, + }} + + t.Logf("qos Classifiers config: %v", classifiers) + for _, tc := range classifiers { + classifier := q.GetOrCreateClassifier(tc.name) + classifier.SetName(tc.name) + classifier.SetType(tc.classType) + term, err := classifier.NewTerm(tc.termID) + if err != nil { + t.Fatalf("Failed to create classifier.NewTerm(): %v", err) + } + + term.SetId(tc.termID) + action := term.GetOrCreateActions() + action.SetTargetGroup(tc.targetGroup) + + // remark := action.GetOrCreateRemark() + // remark.SetDscp = ygot.Uint8(0) + + condition := term.GetOrCreateConditions() + if tc.name == "dscp_based_classifier_ipv4" { + condition.GetOrCreateIpv4().SetDscpSet(tc.dscpSet) + } else if tc.name == "dscp_based_classifier_ipv6" { + condition.GetOrCreateIpv6().SetDscpSet(tc.dscpSet) + } + // else if tc.name == "exp_based_classifier_mpls" { + // t.Log("creating MPLS") + // condition.GetOrCreateMpls().SetTrafficClass(tc.expSet[0]) + // } + + gnmi.Replace(t, dut, gnmi.OC().Qos().Config(), q) + } + + t.Logf("Create qos input classifier config") + classifierIntfs := []struct { + desc string + intf string + inputClassifierType oc.E_Input_Classifier_Type + classifier string + }{{ + desc: "Input Classifier Type IPV4", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV4, + classifier: "dscp_based_classifier_ipv4", + }, { + desc: "Input Classifier Type IPV6", + intf: dp1.Name(), + inputClassifierType: oc.Input_Classifier_Type_IPV6, + classifier: "dscp_based_classifier_ipv6", + // }, { + // desc: "Input Classifier Type MPLS", + // intf: dp1.Name(), + // inputClassifierType: oc.Input_Classifier_Type_MPLS, + // classifier: "exp_based_classifier_mpls", + }} + t.Logf("qos input classifier config: %v", classifierIntfs) + for _, tc := range classifierIntfs { + qoscfg.SetInputClassifier(t, dut, q, tc.intf, tc.inputClassifierType, tc.classifier) + } + t.Logf("qos input remark config: %v", classifierIntfs) + if deviations.QosRemarkOCUnsupported(dut) { + configureRemarkIpv4(t, dut) + configureRemarkIpv6(t, dut) + configureRemarkMpls(t, dut) + } +} + +func configureRemarkMpls(t *testing.T, dut *ondatra.DUTDevice) { + jsonConfig := (` + qos map exp 5 to traffic-class 4 + qos map exp 7 to traffic-class 6 + qos map traffic-class 4 to exp 4 + qos map traffic-class 6 to exp 6 + `) + helpers.GnmiCLIConfig(t, dut, jsonConfig) +} + +func configureRemarkIpv4(t *testing.T, dut *ondatra.DUTDevice) { + jsonConfig := ` + policy-map type quality-of-service __yang_[IPV4__dscp_based_classifier_ipv4][IPV6__dscp_based_classifier_ipv6] + class __yang_[dscp_based_classifier_ipv4]_[4] + set dscp 4 + class __yang_[dscp_based_classifier_ipv4]_[6] + set dscp 6 + ` + helpers.GnmiCLIConfig(t, dut, jsonConfig) +} + +func configureRemarkIpv6(t *testing.T, dut *ondatra.DUTDevice) { + jsonConfig := ` + policy-map type quality-of-service __yang_[IPV4__dscp_based_classifier_ipv4][IPV6__dscp_based_classifier_ipv6] + class __yang_[dscp_based_classifier_ipv6]_[0] + set dscp 0 + class __yang_[dscp_based_classifier_ipv6]_[1] + set dscp 8 + class __yang_[dscp_based_classifier_ipv6]_[3] + set dscp 16 + class __yang_[dscp_based_classifier_ipv6]_[2] + set dscp 24 + class __yang_[dscp_based_classifier_ipv6]_[4] + set dscp 32 + class __yang_[dscp_based_classifier_ipv6]_[6] + set dscp 48 + ` + helpers.GnmiCLIConfig(t, dut, jsonConfig) +} + +// configureATE sets up the ATE interfaces and BGP configurations. +func configureATE(t *testing.T) gosnappi.Config { + topo := gosnappi.NewConfig() + t.Log("Configure ATE interface") + port1 := topo.Ports().Add().SetName("port1") + port2 := topo.Ports().Add().SetName("port2") + + port1Dev := topo.Devices().Add().SetName(atePort1.Name + ".dev") + port1Eth := port1Dev.Ethernets().Add().SetName(atePort1.Name + ".Eth").SetMac(atePort1.MAC) + port1Eth.Connection().SetPortName(port1.Name()) + port1Ipv4 := port1Eth.Ipv4Addresses().Add().SetName(atePort1.Name + ".IPv4") + port1Ipv4.SetAddress(atePort1.IPv4).SetGateway(dutPort1.IPv4).SetPrefix(uint32(atePort1.IPv4Len)) + port1Ipv6 := port1Eth.Ipv6Addresses().Add().SetName(atePort1.Name + ".IPv6") + port1Ipv6.SetAddress(atePort1.IPv6).SetGateway(dutPort1.IPv6).SetPrefix(uint32(atePort1.IPv6Len)) + + port2Dev := topo.Devices().Add().SetName(atePort2.Name + ".dev") + port2Eth := port2Dev.Ethernets().Add().SetName(atePort2.Name + ".Eth").SetMac(atePort2.MAC) + port2Eth.Connection().SetPortName(port2.Name()) + port2Ipv4 := port2Eth.Ipv4Addresses().Add().SetName(atePort2.Name + ".IPv4") + port2Ipv4.SetAddress(atePort2.IPv4).SetGateway(dutPort2.IPv4).SetPrefix(uint32(atePort2.IPv4Len)) + port2Ipv6 := port2Eth.Ipv6Addresses().Add().SetName(atePort2.Name + ".IPv6") + port2Ipv6.SetAddress(atePort2.IPv6).SetGateway(dutPort2.IPv6).SetPrefix(uint32(atePort2.IPv6Len)) + + return topo +} + +func rewriteIpv6PktsWithDscp(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config, greTest bool, gueTest bool) { + + topo.Flows().Clear() + var trafficIds []string + + const max = maxIpv6Tos + for value := 0; value < max; value++ { + trafficID := fmt.Sprintf("ipv6-traffic-tos%v", value) + trafficIds = append(trafficIds, trafficID) + t.Logf("Configuring flow %s", trafficID) + flow := topo.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv6"}).SetRxNames([]string{atePort2.Name + ".IPv6"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + ipHeader := flow.Packet().Add().Ipv6() + ipHeader.Src().SetValue(atePort1.IPv6) + ipHeader.Dst().SetValue(atePort2.IPv6) + ipHeader.TrafficClass().SetValue(uint32(value)) + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + } + + ate.OTG().PushConfig(t, topo) + + if greTest { + // configure GRE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv6", "gre", true) + } + + if gueTest { + // configure GUE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv6", "ipv6-over-udp", true) + } + + intialpacket1 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "0") + intialpacket2 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "1") + intialpacket3 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "2") + intialpacket4 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "3") + intialpacket5 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "4") + intialpacket6 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "6") + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "ipv6-traffic-tos0") + stopCapture(t, ate) + + if greTest { + // configure GRE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv6", "gre", false) + } + + if gueTest { + // configure GUE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv6", "ipv6-over-udp", false) + } + + for _, trafficID := range trafficIds { + t.Logf("Verify Traffic flow %s", trafficID) + verifyTrafficFlow(t, ate, trafficID) + } + + if greTest { + checkGreCapture(t, ate, "port2", "ipv6") + } else if gueTest { + checkGueCapture(t, ate, "port2", "ipv6") + } else { + verifyIpv6DscpCapture(t, ate, "port2") + } + + finalpacket1 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "0") + finalpacket2 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "1") + finalpacket3 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "2") + finalpacket4 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "3") + finalpacket5 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "4") + finalpacket6 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV4, "6") + + compare_counters(t, intialpacket1, finalpacket1) + compare_counters(t, intialpacket2, finalpacket2) + compare_counters(t, intialpacket3, finalpacket3) + compare_counters(t, intialpacket4, finalpacket4) + compare_counters(t, intialpacket5, finalpacket5) + compare_counters(t, intialpacket6, finalpacket6) +} + +func rewriteIpv4PktsWithDscp(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config, greTest bool, gueTest bool) { + type trafficData struct { + frameSize uint32 + dscp uint8 + queue string + inputIntf attrs.Attributes + } + + queues := netutil.CommonTrafficQueues(t, dut) + IngressIPv4TrafficFlows := map[string]*trafficData{ + "intf1-nc1-ipv4": { + frameSize: frameSize, + dscp: 6, + queue: queues.NC1, + inputIntf: atePort1, + }, + "intf1-nc1-ipv4_dscp7": { + frameSize: frameSize, + dscp: 7, + queue: queues.NC1, + inputIntf: atePort1, + }, + "intf1-af4-ipv4": { + frameSize: frameSize, + dscp: 4, + queue: queues.AF4, + inputIntf: atePort1, + }, + "intf1-af4-ipv4_dscp5": { + frameSize: frameSize, + dscp: 5, + queue: queues.AF4, + inputIntf: atePort1, + }, + "intf1-af3-ipv4": { + frameSize: frameSize, + dscp: 3, + queue: queues.AF3, + inputIntf: atePort1, + }, + "intf1-af2-ipv4": { + frameSize: frameSize, + dscp: 2, + queue: queues.AF2, + inputIntf: atePort1, + }, + "intf1-af1-ipv4": { + frameSize: frameSize, + dscp: 1, + queue: queues.AF1, + inputIntf: atePort1, + }, + "intf1-be1-ipv4": { + frameSize: frameSize, + dscp: 0, + queue: queues.BE1, + inputIntf: atePort1, + }, + } + + topo.Flows().Clear() + + for trafficID, data := range IngressIPv4TrafficFlows { + t.Logf("Configuring flow %s", trafficID) + flow := topo.Flows().Add().SetName(trafficID) + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{data.inputIntf.Name + ".IPv4"}).SetRxNames([]string{atePort2.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(data.inputIntf.MAC) + ethHeader.Dst().Auto() + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(data.inputIntf.IPv4) + ipHeader.Dst().SetValue(atePort2.IPv4) + ipHeader.Priority().Dscp().Phb().SetValue(uint32(data.dscp)) + flow.Size().SetFixed(uint32(data.frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + } + + ate.OTG().PushConfig(t, topo) + if greTest { + // configure GRE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv4", "gre", true) + } + if gueTest { + // configure GUE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv4", "ipv4-over-udp", true) + } + + intialpacket1 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "0") + intialpacket2 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "1") + intialpacket3 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "2") + intialpacket4 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "3") + intialpacket5 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "4") + intialpacket6 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "6") + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "intf1-nc1-ipv4") + stopCapture(t, ate) + + if greTest { + // configure GRE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv4", "gre", false) + } + if gueTest { + // configure GUE encapsulation with Traffic policy + configureGreGuePolicyForwarding(t, dut, "ipv4", "ipv4-over-udp", false) + } + + for trafficID := range IngressIPv4TrafficFlows { + t.Logf("Verify Traffic flow %s", trafficID) + verifyTrafficFlow(t, ate, trafficID) + } + if greTest { + checkGreCapture(t, ate, "port2", "ipv4") + } else if gueTest { + checkGueCapture(t, ate, "port2", "ipv4") + } else { + verifyIpv4DscpCapture(t, ate, "port2") + } + finalpacket1 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "0") + finalpacket2 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "1") + finalpacket3 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "2") + finalpacket4 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "3") + finalpacket5 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "4") + finalpacket6 := verfiy_classifier_packets(t, dut, oc.Input_Classifier_Type_IPV6, "6") + + compare_counters(t, intialpacket1, finalpacket1) + compare_counters(t, intialpacket2, finalpacket2) + compare_counters(t, intialpacket3, finalpacket3) + compare_counters(t, intialpacket4, finalpacket4) + compare_counters(t, intialpacket5, finalpacket5) + compare_counters(t, intialpacket6, finalpacket6) + +} + +func rewriteMplsSwapAction(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config) { + + topo.Flows().Clear() + + t.Logf("Configuring flow for MPLS Swap Action") + flow := topo.Flows().Add().SetName("MplsSwap") + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv4"}).SetRxNames([]string{atePort2.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(mplsSwapLabel) + mpls.TrafficClass().SetValue(uint32(5)) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(atePort1.IPv4) + ipHeader.Dst().SetValue(atePort2.IPv4) + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + + ate.OTG().PushConfig(t, topo) + + t.Logf("Configuring MPLS Swap Action with DUT") + + cfgplugins.NewStaticMplsLspSwapLabel(t, dut, "lsp-swap", mplsSwapLabel, atePort2.IPv4, mplsSwapLabelTo, lspNextHopIndex) + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "MplsSwap") + stopCapture(t, ate) + t.Logf("Verify Traffic flow MplsSwap") + verifyTrafficFlow(t, ate, "MplsSwap") + verifyMplsSwapPushCapture(t, ate, "port2", mplsSwapLabelTo, true) + + cfgplugins.RemoveStaticMplsLspSwapLabel(t, dut, "lsp-swap", mplsSwapLabel, atePort2.IPv4, mplsSwapLabelTo) + +} + +func rewriteIpv4MplsPopAction(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config) { + + topo.Flows().Clear() + + t.Logf("Configuring flow for MPLS V4 POP Action") + flow := topo.Flows().Add().SetName("MplsPopV4") + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv4"}).SetRxNames([]string{atePort2.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(mplsPopLabelv4) + + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(atePort1.IPv4) + ipHeader.Dst().SetValue(atePort2.IPv4) + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + + ate.OTG().PushConfig(t, topo) + + t.Logf("Configuring MPLS POP Action for IPv4 with DUT") + + cfgplugins.NewStaticMplsLspPopLabel(t, dut, "lsp-pop", mplsPopLabelv4, "", atePort2.IPv4, "ipv4") + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "MplsPopV4") + stopCapture(t, ate) + t.Logf("Verify Traffic flow MplsPopV4") + verifyTrafficFlow(t, ate, "MplsPopV4") + + verifyMplsPopCapture(t, ate, "port2") + + cfgplugins.RemoveStaticMplsLspPopLabel(t, dut, "lsp-pop", mplsPopLabelv4, "", atePort2.IPv4, "ipv4") + +} + +func rewriteIpv6MplsPopAction(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config) { + + topo.Flows().Clear() + + t.Logf("Configuring flow for MPLS V4 POP Action") + flow := topo.Flows().Add().SetName("MplsPopV6") + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv6"}).SetRxNames([]string{atePort2.Name + ".IPv6"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + + mpls := flow.Packet().Add().Mpls() + mpls.Label().SetValue(mplsPopLabelv6) + ip6 := flow.Packet().Add().Ipv6() + ip6.Src().SetValue(atePort1.IPv6) + ip6.Dst().SetValue(atePort2.IPv6) + + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + + ate.OTG().PushConfig(t, topo) + + t.Logf("Configuring MPLS POP Action for IPv6 with DUT") + + cfgplugins.NewStaticMplsLspPopLabel(t, dut, "lsp-pop-v6", mplsPopLabelv6, "", atePort2.IPv6, "ipv6") + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "MplsPopV6") + stopCapture(t, ate) + t.Logf("Verify Traffic flow MplsPopV6") + verifyTrafficFlow(t, ate, "MplsPopV6") + verifyMplsPopCapture(t, ate, "port2") + + cfgplugins.RemoveStaticMplsLspPopLabel(t, dut, "lsp-pop-v6", mplsPopLabelv6, "", atePort2.IPv6, "ipv6") + +} + +func rewriteIpv4MplsPushAction(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config) { + + dp1 := dut.Port(t, "port1") + topo.Flows().Clear() + + t.Logf("Configuring flow for MPLS V4 PUSH Action") + flow := topo.Flows().Add().SetName("MplsPushV4") + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv4"}).SetRxNames([]string{atePort2.Name + ".IPv4"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + ipHeader := flow.Packet().Add().Ipv4() + ipHeader.Src().SetValue(atePort1.IPv4) + ipHeader.Dst().SetValue(ipv4DestAddr) + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + ate.OTG().PushConfig(t, topo) + + t.Logf("Configuring MPLS Push Action with DUT") + + cfgplugins.NewStaticMplsLspPushLabel(t, dut, "mpls-lsp-push", dp1.Name(), atePort2.IPv4, ipv4DestAddrWithCidr, + mplsPushLabelV4, lspNextHopIndex, "ipv4") + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "MplsPushV4") + stopCapture(t, ate) + + t.Logf("Verify Traffic flow MplsPushV4") + verifyTrafficFlow(t, ate, "MplsPushV4") + verifyMplsSwapPushCapture(t, ate, "port2", mplsPushLabelV4, false) + + cfgplugins.RemoveStaticMplsLspPushLabel(t, dut, "mpls-lsp-push", dp1.Name()) + +} + +func rewriteIpv6MplsPushAction(t *testing.T, dut *ondatra.DUTDevice, ate *ondatra.ATEDevice, topo gosnappi.Config) { + + dp1 := dut.Port(t, "port1") + topo.Flows().Clear() + + t.Logf("Configuring flow for MPLS V6 PUSH Action") + flow := topo.Flows().Add().SetName("MplsPushV6") + flow.Metrics().SetEnable(true) + flow.TxRx().Device().SetTxNames([]string{atePort1.Name + ".IPv6"}).SetRxNames([]string{atePort2.Name + ".IPv6"}) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().Auto() + ip6 := flow.Packet().Add().Ipv6() + ip6.Src().SetValue(atePort1.IPv6) + ip6.Dst().SetValue(ipv6DestAddr) + flow.Size().SetFixed(uint32(frameSize)) + flow.Rate().SetPps(packetPerSecond) + flow.Duration().FixedPackets().SetPackets(packetPerSecond) + + ate.OTG().PushConfig(t, topo) + + t.Logf("Configuring MPLS Push Action with DUT") + + cfgplugins.NewStaticMplsLspPushLabel(t, dut, "mpls-lsp-push-ipv6", dp1.Name(), atePort2.IPv6, ipv6DestAddrWithCidr, + mplsPushLabelV6, lspNextHopIndex, "ipv6") + + startCapture(t, ate) + trafficStartStop(t, ate, topo, "MplsPushV6") + stopCapture(t, ate) + + t.Logf("Verify Traffic flow MplsPushV6") + verifyTrafficFlow(t, ate, "MplsPushV6") + + verifyMplsSwapPushCapture(t, ate, "port2", mplsPushLabelV6, false) + + cfgplugins.RemoveStaticMplsLspPushLabel(t, dut, "mpls-lsp-push-ipv6", dp1.Name()) +} + +func trafficStartStop(t *testing.T, ate *ondatra.ATEDevice, config gosnappi.Config, flowName string) { + ate.OTG().StartProtocols(t) + otgutils.WaitForARP(t, ate.OTG(), config, "IPv4") + otgutils.WaitForARP(t, ate.OTG(), config, "IPv6") + ate.OTG().StartTraffic(t) + time.Sleep(trafficSleepTime * time.Second) + ate.OTG().StopTraffic(t) + otgutils.LogFlowMetrics(t, ate.OTG(), config) +} + +func verifyTrafficFlow(t *testing.T, ate *ondatra.ATEDevice, flowName string) { + recvMetricV4 := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(flowName).State()) + + framesTxV4 := recvMetricV4.GetCounters().GetOutPkts() + framesRxV4 := recvMetricV4.GetCounters().GetInPkts() + + t.Logf("Starting traffic validation for flow %s", flowName) + if framesTxV4 == 0 { + t.Error("No traffic was generated and frames transmitted were 0") + } else if framesRxV4 == framesTxV4 { + t.Logf("Traffic validation successful for [%s] FramesTx: %d FramesRx: %d", flowName, framesTxV4, framesRxV4) + } else { + t.Errorf("Traffic validation failed for [%s] FramesTx: %d FramesRx: %d", flowName, framesTxV4, framesRxV4) + } +} + +// startCapture starts the capture on the otg ports +func startCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) +} + +// stopCapture starts the capture on the otg ports +func stopCapture(t *testing.T, ate *ondatra.ATEDevice) { + otg := ate.OTG() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, cs) +} + +func enableCapture(t *testing.T, config gosnappi.Config, port string) { + + config.Captures().Clear() + t.Log("Enabling capture on ", port) + config.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) + +} + +func processCapture(t *testing.T, ate *ondatra.ATEDevice, port string) string { + otg := ate.OTG() + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(port)) + time.Sleep(captureWait * time.Second) + pcapFile, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := pcapFile.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + defer pcapFile.Close() + return pcapFile.Name() +} + +func verifyMplsSwapPushCapture(t *testing.T, ate *ondatra.ATEDevice, port string, expLabel int, checkExp bool) { + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + found := false + foundExp := false + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + if mplsLayer := packet.Layer(layers.LayerTypeMPLS); mplsLayer != nil { + label, _ := mplsLayer.(*layers.MPLS) + labelValue := label.Label + if labelValue == uint32(expLabel) { + found = true + t.Logf("Mpls Label Swapped/Pushed as expected, Got: %v", labelValue) + if checkExp { + expLabel = int(label.TrafficClass) + if expLabel == 4 { + foundExp = true + t.Logf("Mpls EXP bit remarked as expected, Got: %v", expLabel) + } else { + t.Fatalf("Mpls Exp bit not remarked as expected, Got: %v", expLabel) + } + } + break + } + + } + + } + if !found { + t.Fatalf(" MPLS swap did not occur %v", expLabel) + } + if checkExp { + if !foundExp { + t.Fatalf(" MPLS EXP bit not remarked") + } + } + +} + +func verifyMplsPopCapture(t *testing.T, ate *ondatra.ATEDevice, port string) { + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + found := false + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + if mplsLayer := packet.Layer(layers.LayerTypeMPLS); mplsLayer != nil { + found = true + } + } + if found { + t.Fatalf(" MPLS POP did not occur") + } +} + +func verifyIpv4DscpCapture(t *testing.T, ate *ondatra.ATEDevice, port string) { + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv4) + if ip.SrcIP.Equal(net.ParseIP(atePort1.IPv4)) { + dscpValue := ip.TOS >> 2 + switch dscpValue { + case 5: + t.Fatalf("Error: DSCP value 5 should be converted to 4 by ingress DUT") + case 7: + t.Fatalf("Error: DSCP value 7 should be converted to 6 by ingress DUT") + } + } + } + } +} + +func verifyIpv6DscpCapture(t *testing.T, ate *ondatra.ATEDevice, port string) { + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + var dscpValuesToConvert []int + for i := 1; i <= maxIpv6Tos; i++ { + if i%8 != 0 { + dscpValuesToConvert = append(dscpValuesToConvert, i) + } + } + + for packet := range packetSource.Packets() { + if ipLayer := packet.Layer(layers.LayerTypeIPv6); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv6) + if ip.SrcIP.Equal(net.ParseIP(atePort1.IPv6)) { + dscpValue := ip.TrafficClass >> 2 + if contains(dscpValuesToConvert, int(dscpValue)) { + t.Fatalf("Error: DSCP value %v should be converted by ingress DUT but not converted", dscpValue) + } + } + } + } +} + +func contains(arr []int, target int) bool { + for _, element := range arr { + if element == target { + return true + } + } + return false +} + +func verfiy_classifier_packets(t *testing.T, dut *ondatra.DUTDevice, classifier oc.E_Input_Classifier_Type, termId string) uint64 { + dp1 := dut.Port(t, "port1") + const timeout = 10 * time.Second + isPresent := func(val *ygnmi.Value[uint64]) bool { return val.IsPresent() } + + _, ok := gnmi.WatchAll(t, dut, gnmi.OC().Qos().Interface(dp1.Name()).Input().ClassifierAny().Term(termId).MatchedPackets().State(), timeout, isPresent).Await(t) + if !ok { + t.Errorf("Unable to find matched packets") + } + matchpackets := gnmi.Get(t, dut, gnmi.OC().Qos().Interface(dp1.Name()).Input().Classifier(classifier).Term(termId).MatchedPackets().State()) + return matchpackets +} + +func compare_counters(t *testing.T, intialpacket uint64, finalpacket uint64) { + t.Logf("Classifier counters Before Traffic %v", intialpacket) + t.Logf("Classifier counters After Traffic %v", finalpacket) + if finalpacket > intialpacket { + t.Logf("Pass : Classifier counters got incremented after start and stop traffic") + } else { + t.Errorf("Fail : Classifier counters not incremented after start and stop traffic. Refer BUG ID 419618177") + } +} + +func configureGreGuePolicyForwarding(t *testing.T, dut *ondatra.DUTDevice, ipType string, greOrGue string, config bool) { + if deviations.PolicyForwardingToNextHopOcUnsupported(dut) || deviations.PolicyForwardingGreEncapsulationOcUnsupported(dut) { + t.Logf("Configuring pf through CLI") + configureGreGuePolicyForwardingFromCLI(t, dut, ipType, greOrGue, config) + } else { + t.Logf("Configuring pf through OC") + configurePolicyForwardingFromOC(t, dut, ipType, greOrGue, config) + } +} + +func configureGreGuePolicyForwardingFromCLI(t *testing.T, dut *ondatra.DUTDevice, ipType string, greOrGue string, config bool) { + interfaceName := dut.Port(t, "port1").Name() + switch dut.Vendor() { + case ondatra.ARISTA: + var matchRules, trafficPolicyConfig string + if config { + if ipType == "ipv4" { + matchRules += fmt.Sprintf(` + match rule-src1-v4 ipv4 + destination prefix %s/32 + actions + count + redirect next-hop group SRC1_NH + nexthop-group SRC1_NH type %s + tunnel-source intf %s + fec hierarchical + entry 1 tunnel-destination %s + `, atePort2.IPv4, greOrGue, dutlo0Attrs.Name, ipv4DestAddr) + } else { + matchRules += fmt.Sprintf(` + + match rule-src1-v6 ipv6 + destination prefix %s/128 + actions + count + redirect next-hop group SRC1_NH + nexthop-group SRC1_NH type %s + tunnel-source intf %s + fec hierarchical + entry 2 tunnel-destination %s + `, atePort2.IPv6, greOrGue, dutlo0Attrs.Name, ipv4DestAddr) + } + + // Apply Policy on the interface + trafficPolicyConfig = fmt.Sprintf(` + tunnel type ipv4-over-udp udp destination port 6080 + tunnel type ipv6-over-udp udp destination port 6080 + traffic-policies + traffic-policy %s + %s + interface %s + traffic-policy input %s + `, trafficPolicyName, matchRules, interfaceName, trafficPolicyName) + } else { + trafficPolicyConfig = fmt.Sprintf(` + no nexthop-group SRC1_NH type %s + interface %s + no traffic-policy input %s + traffic-policies + no traffic-policy %s + `, greOrGue, interfaceName, trafficPolicyName, trafficPolicyName) + } + helpers.GnmiCLIConfig(t, dut, trafficPolicyConfig) + + default: + t.Errorf("Deviation configureGreGuePolicyForwardingFromCLI is not handled for the dut: %v", dut.Vendor()) + } +} + +func configurePolicyForwardingFromOC(t *testing.T, dut *ondatra.DUTDevice, ipType string, greOrGue string, config bool) { + interfaceName := dut.Port(t, "port1").Name() + pf := &oc.Root{} + ni := pf.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + if config { + policy := ni.GetOrCreatePolicyForwarding().GetOrCreatePolicy(trafficPolicyName) + policy.Type = oc.Policy_Type_PBR_POLICY + if ipType == "ipv4" { + // Rule 1: Match IPV4-SRC1 and accept/forward + rule1 := policy.GetOrCreateRule(1) + rule1.GetOrCreateIpv4().DestinationAddress = ygot.String(fmt.Sprintf("%s/32", dutPort2.IPv4)) + encapGre4 := rule1.GetOrCreateAction().GetOrCreateEncapsulateGre() + targetName := greOrGue + encapGre4.GetOrCreateTarget(targetName).Source = ygot.String(dutlo0Attrs.IPv4) + encapGre4.GetOrCreateTarget(targetName).Destination = ygot.String(ipv4DestAddr) + + } else { + // Rule 2: Match IPV6-SRC1 and accept/forward + rule4 := policy.GetOrCreateRule(4) + rule4.GetOrCreateIpv6().DestinationAddress = ygot.String(fmt.Sprintf("%s/128", dutPort2.IPv6)) + encapGre6 := rule4.GetOrCreateAction().GetOrCreateEncapsulateGre() + targetName := greOrGue + encapGre6.GetOrCreateTarget(targetName).Source = ygot.String(dutlo0Attrs.IPv6) + encapGre6.GetOrCreateTarget(targetName).Destination = ygot.String(ipv6DestAddr) + } + + // Apply the policy to DUT Port 1 + ni.GetOrCreatePolicyForwarding().GetOrCreateInterface(interfaceName).ApplyForwardingPolicy = ygot.String(trafficPolicyName) + gnmi.Replace(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Config(), ni.PolicyForwarding) + } else { + pfPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Interface(interfaceName) + gnmi.Delete(t, dut, pfPath.Config()) + } +} + +func configureLoopbackInterface(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + dc := gnmi.OC() + loopbackIntfName := netutil.LoopbackInterface(t, dut, 0) + dutlo0Attrs.Name = loopbackIntfName + 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) + } +} + +func configureStaticRoutes(t *testing.T, dut *ondatra.DUTDevice) { + + configStaticRoute(t, dut, "192.0.2.0/30", "192.0.2.10", "0") + configStaticRoute(t, dut, ipv4DestAddrWithCidr, atePort2.IPv4, "1") + configStaticRoute(t, dut, "2001:DB8:0::0/126", "2001:DB8:0::10", "0") + configStaticRoute(t, dut, ipv6DestAddrWithCidr, atePort2.IPv6, "1") +} + +// Congigure Static Routes on DUT +func configStaticRoute(t *testing.T, dut *ondatra.DUTDevice, prefix string, nexthop string, index string) { + b := &gnmi.SetBatch{} + if nexthop == "Null0" { + nexthop = "DROP" + } + routeCfg := &cfgplugins.StaticRouteCfg{ + NetworkInstance: deviations.DefaultNetworkInstance(dut), + Prefix: prefix, + NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ + index: oc.UnionString(nexthop), + }, + } + if _, err := cfgplugins.NewStaticRouteCfg(b, routeCfg, dut); err != nil { + t.Fatalf("Failed to configure static route: %v", err) + } + b.Set(t, dut) +} + +func checkGreCapture(t *testing.T, ate *ondatra.ATEDevice, port string, ipType string) { + var innerLayerType gopacket.LayerType + switch ipType { + case "ipv4": + innerLayerType = layers.LayerTypeIPv4 + case "ipv6": + innerLayerType = layers.LayerTypeIPv6 + } + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipOuterLayer, ok := ipLayer.(*layers.IPv4) + if !ok || ipOuterLayer == nil { + t.Errorf("Outer IP layer not found %d", ipLayer) + return + } + greLayer := packet.Layer(layers.LayerTypeGRE) + grePacket, ok := greLayer.(*layers.GRE) + if !ok || grePacket == nil { + t.Error("GRE layer not found") + return + } + if ipOuterLayer.Protocol != greProtocol { + t.Errorf("Packet is not encapslated properly. Encapsulated protocol is: %d", ipOuterLayer.Protocol) + return + } + innerPacket := gopacket.NewPacket(grePacket.Payload, grePacket.NextLayerType(), gopacket.Default) + ipInnerLayer := innerPacket.Layer(innerLayerType) + if ipInnerLayer == nil { + t.Error("Inner IP layer not found") + return + } + var innerPacketTOS, dscp uint8 + switch ipType { + case "ipv4": + ipInnerPacket, ok := ipInnerLayer.(*layers.IPv4) + if !ok || ipInnerPacket == nil { + t.Errorf("Inner layer of type %s not found", innerLayerType.String()) + return + } + innerPacketTOS = ipInnerPacket.TOS + dscp = innerPacketTOS >> 2 + switch dscp { + case 5: + t.Fatalf("Error: DSCP value 5 should be converted to 4 by ingress DUT") + case 7: + t.Fatalf("Error: DSCP value 7 should be converted to 6 by ingress DUT") + } + case "ipv6": + var dscpValuesToConvert []int + for i := 1; i <= maxIpv6Tos; i++ { + if i%8 != 0 { + dscpValuesToConvert = append(dscpValuesToConvert, i) + } + } + ipInnerPacket, ok := ipInnerLayer.(*layers.IPv6) + if !ok || ipInnerPacket == nil { + t.Errorf("Inner layer of type %s not found", innerLayerType.String()) + return + } + innerPacketTOS = ipInnerPacket.TrafficClass + dscp = innerPacketTOS + if contains(dscpValuesToConvert, int(dscp)) { + t.Fatalf("Error: DSCP value %v should be converted by ingress DUT but not converted", dscp) + } + } + } +} + +func checkGueCapture(t *testing.T, ate *ondatra.ATEDevice, port string, ipType string) { + pcapfilename := processCapture(t, ate, port) + handle, err := pcap.OpenOffline(pcapfilename) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + for packet := range packetSource.Packets() { + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipOuterLayer, ok := ipLayer.(*layers.IPv4) + if !ok || ipOuterLayer == nil { + t.Errorf("Outer IP layer not found %d", ipLayer) + return + } + + udpLayer := packet.Layer(layers.LayerTypeUDP) + udp, ok := udpLayer.(*layers.UDP) + if !ok || udp == nil { + t.Error("GUE layer not found") + return + } else { + if udp.DstPort == gueProtocolPort { + t.Log("Got the encapsulated GUE layer") + } + + var innerPacketTOS, dscp uint8 + + switch ipType { + case "ipv4": + innerPacket := gopacket.NewPacket(udp.Payload, layers.LayerTypeIPv4, gopacket.Default) + ipLayer := innerPacket.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + t.Errorf("Inner layer of type %s not found", ipType) + return + } + ip, _ := ipLayer.(*layers.IPv4) + innerPacketTOS = ip.TOS + dscp := innerPacketTOS >> 2 + switch dscp { + case 5: + t.Fatalf("Error: DSCP value 5 should be converted to 4 by ingress DUT") + case 7: + t.Fatalf("Error: DSCP value 7 should be converted to 6 by ingress DUT") + } + + case "ipv6": + var dscpValuesToConvert []int + for i := 1; i <= maxIpv6Tos; i++ { + if i%8 != 0 { + dscpValuesToConvert = append(dscpValuesToConvert, i) + } + } + innerPacket := gopacket.NewPacket(udp.Payload, layers.LayerTypeIPv6, gopacket.Default) + ipLayer := innerPacket.Layer(layers.LayerTypeIPv6) + if ipLayer == nil { + t.Errorf("Inner layer of type %s not found", ipType) + return + } + ip, _ := ipLayer.(*layers.IPv6) + + innerPacketTOS = ip.TrafficClass + dscp = innerPacketTOS + println(dscp) + if contains(dscpValuesToConvert, int(dscp)) { + t.Fatalf("Error: DSCP value %v should be converted by ingress DUT but not converted. ISSUE ID #434618050 raised ", dscp) + } + } + } + } +} diff --git a/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/metadata.textproto b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/metadata.textproto new file mode 100644 index 00000000000..a21b4d4b788 --- /dev/null +++ b/feature/qos/otg_tests/ingress_traffic_classification_and_rewrite_test/metadata.textproto @@ -0,0 +1,20 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "78f324b0-d069-45a7-ba7d-a76817783a27" +plan_id: "DP-1.16" +description: "Ingress traffic classification and rewrite" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + qos_remark_oc_unsupported: true + static_mpls_lsp_oc_unsupported: true + policy_forwarding_to_next_hop_oc_unsupported: true + policy_forwarding_gre_encapsulation_oc_unsupported: true + } +} diff --git a/feature/qos/otg_tests/one_sp_queue_traffic_test/README.md b/feature/qos/otg_tests/one_sp_queue_traffic_test/README.md index 2c357ec3166..a5b57b0e76e 100644 --- a/feature/qos/otg_tests/one_sp_queue_traffic_test/README.md +++ b/feature/qos/otg_tests/one_sp_queue_traffic_test/README.md @@ -66,7 +66,7 @@ Verify that DUT drops AF4, AF3, AF2, AF1, BE1 and BE0 before NC1. * Configure strict priority queue for NC1. * Configure WRR for AF4, AF3, AF2, AF1, BE1 and BE0 with weight 48, 12, 8, - 4, 2 and 1 respectively. + 4, 1 and 1 respectively. * NC1 vs AF4 traffic test @@ -91,6 +91,99 @@ Verify that DUT drops AF4, AF3, AF2, AF1, BE1 and BE0 before NC1. * NC1 vs AF1 * NC1 vs BE1 * NC1 vs BE0 +#### Canonical OC +```json +{ + "qos": { + "scheduler-policies": { + "scheduler-policy": [ + { + "config": { + "name": "0" + }, + "name": "0", + "schedulers": { + "scheduler": [ + { + "config": { + "sequence": 0 + }, + "inputs": { + "input": [ + { + "config": { + "id": "NC1", + "weight": "100" + }, + "id": "NC1" + } + ] + }, + "sequence": 0 + } + ] + } + }, + { + "config": { + "name": "1" + }, + "name": "1", + "schedulers": { + "scheduler": [ + { + "config": { + "sequence": 1 + }, + "inputs": { + "input": [ + { + "config": { + "id": "AF1", + "weight": "4" + }, + "id": "AF1" + }, + { + "config": { + "id": "AF2", + "weight": "48" + }, + "id": "AF2" + }, + { + "config": { + "id": "AF3", + "weight": "12" + }, + "id": "AF3" + }, + { + "config": { + "id": "BE0", + "weight": "1" + }, + "id": "BE0" + }, + { + "config": { + "id": "BE1", + "weight": "1" + }, + "id": "BE1" + } + ] + }, + "sequence": 1 + } + ] + } + } + ] + } + } +} +``` ## Config parameter coverage @@ -191,4 +284,4 @@ paths: rpcs: gnmi: gNMI.Set: - Replace: \ No newline at end of file + Replace: diff --git a/feature/qos/otg_tests/one_sp_queue_traffic_test/one_sp_queue_traffic_test.go b/feature/qos/otg_tests/one_sp_queue_traffic_test/one_sp_queue_traffic_test.go index 85382c483f1..f1ffac1a0e4 100644 --- a/feature/qos/otg_tests/one_sp_queue_traffic_test/one_sp_queue_traffic_test.go +++ b/feature/qos/otg_tests/one_sp_queue_traffic_test/one_sp_queue_traffic_test.go @@ -1050,7 +1050,7 @@ func ConfigureQoS(t *testing.T, dut *ondatra.DUTDevice) { priority: oc.Scheduler_Priority_UNSET, inputID: "BE0", inputType: oc.Input_InputType_QUEUE, - weight: uint64(2), + weight: uint64(1), queueName: queues.BE0, targetGroup: "target-group-BE0", }, { diff --git a/feature/qos/tests/qos_policy_config_test/README.md b/feature/qos/tests/qos_policy_config_test/README.md index 949e9c4d835..a8f644cde3c 100644 --- a/feature/qos/tests/qos_policy_config_test/README.md +++ b/feature/qos/tests/qos_policy_config_test/README.md @@ -227,4 +227,232 @@ paths: rpcs: gnmi: gNMI.Set: - Replace: \ No newline at end of file + Replace: +``` + +## Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "Input Interface", + "name": "port1", + "type": "ethernetCsmacd" + }, + "name": "port1" + }, + { + "config": { + "description": "Output Interface", + "name": "port2", + "type": "ethernetCsmacd" + }, + "name": "port2" + } + ] + }, + "qos": { + "classifiers": { + "classifier": [ + { + "config": { + "name": "qos-policy" + }, + "name": "qos-policy", + "terms": { + "term": [ + { + "actions": { + "config": { + "target-group": "fg-BE1" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [ + 1, + 2, + 3 + ] + } + } + }, + "config": { + "id": "term1" + }, + "id": "term1" + }, + { + "actions": { + "config": { + "target-group": "fg-NC1" + } + }, + "conditions": { + "ipv4": { + "config": { + "dscp-set": [ + 48, + 49, + 50, + 51, + 52, + 53, + 54, + 55, + 56, + 57, + 58, + 59 + ] + } + } + }, + "config": { + "id": "term2" + }, + "id": "term2" + } + ] + } + } + ] + }, + "forwarding-groups": { + "forwarding-group": [ + { + "config": { + "name": "fg-BE1", + "output-queue": "q-BE1" + }, + "name": "fg-BE1" + }, + { + "config": { + "name": "fg-NC1", + "output-queue": "q-NC1" + }, + "name": "fg-NC1" + } + ] + }, + "interfaces": { + "interface": [ + { + "config": { + "interface-id": "eth0" + }, + "input": { + "classifiers": { + "classifier": [ + { + "config": { + "name": "qos-policy", + "type": "IPV4" + }, + "type": "IPV4" + } + ] + } + }, + "interface-id": "eth0" + }, + { + "config": { + "interface-id": "port2" + }, + "interface-id": "port2", + "output": { + "queues": { + "queue": [ + { + "config": { + "name": "q-BE1" + }, + "name": "q-BE1" + } + ] + }, + "scheduler-policy": { + "config": { + "name": "scheduler-policy" + } + } + } + } + ] + }, + "queues": { + "queue": [ + { + "config": { + "name": "q-BE1" + }, + "name": "q-BE1" + }, + { + "config": { + "name": "q-NC1" + }, + "name": "q-NC1" + } + ] + }, + "scheduler-policies": { + "scheduler-policy": [ + { + "config": { + "name": "scheduler-policy" + }, + "name": "scheduler-policy", + "schedulers": { + "scheduler": [ + { + "config": { + "sequence": 0 + }, + "inputs": { + "input": [ + { + "config": { + "id": "NC1", + "input-type": "QUEUE", + "queue": "q-NC1", + "weight": "200" + }, + "id": "NC1" + } + ] + }, + "sequence": 0 + }, + { + "config": { + "sequence": 1 + }, + "inputs": { + "input": [ + { + "config": { + "id": "BE1", + "input-type": "QUEUE", + "queue": "q-BE1", + "weight": "1" + }, + "id": "BE1" + } + ] + }, + "sequence": 1 + } + ] + } + } + ] + } + } +} +``` diff --git a/feature/qos/tests/qos_policy_config_test/qos_policy_config_test.go b/feature/qos/tests/qos_policy_config_test/qos_policy_config_test.go index 303b7b5b540..e9ee8154879 100644 --- a/feature/qos/tests/qos_policy_config_test/qos_policy_config_test.go +++ b/feature/qos/tests/qos_policy_config_test/qos_policy_config_test.go @@ -18,6 +18,7 @@ import ( "math" "sort" "testing" + "time" "github.com/google/go-cmp/cmp" "github.com/openconfig/featureprofiles/internal/fptest" @@ -2290,7 +2291,8 @@ func testNokiaSchedulerPoliciesConfig(t *testing.T) { if got, want := gnmi.Get(t, dut, outQueue.Name().State()), tc.queueName; got != want { t.Errorf("outQueue.Name().State(): got %v, want %v", got, want) } - if got, want := gnmi.Get(t, dut, outQueue.QueueManagementProfile().State()), "DropProfile"; got != want { + want := "DropProfile" + if got, ok := gnmi.Await(t, dut, outQueue.QueueManagementProfile().State(), 10*time.Second, want).Val(); !ok { t.Errorf("outQueue.QueueManagementProfile().State(): got %v, want %v", got, want) } if got, want := gnmi.Get(t, dut, wredUniform.EnableEcn().State()), ecnConfig.ecnEnabled; got != want { diff --git a/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go b/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go index 8581b9ed14c..79defd900e7 100644 --- a/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go +++ b/feature/security/aaa/kne_tests/tls_authentication_over_grpc_test/tls_authentication_over_grpc_test.go @@ -16,54 +16,26 @@ package tls_authentication_over_grpc_test import ( "context" - "crypto/tls" "encoding/json" - "fmt" "testing" + "time" "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/security/credz" "github.com/openconfig/ondatra" - "github.com/openconfig/ondatra/binding" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" "github.com/openconfig/ygot/ygot" - "golang.org/x/crypto/ssh" - "google.golang.org/grpc" - "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" gpb "github.com/openconfig/gnmi/proto/gnmi" - tpb "github.com/openconfig/kne/proto/topo" ) func TestMain(m *testing.M) { fptest.RunTests(m) } -func keyboardInteraction(password string) ssh.KeyboardInteractiveChallenge { - return func(user, instruction string, questions []string, echos []bool) ([]string, error) { - if len(questions) == 0 { - return []string{}, nil - } - return []string{password}, nil - } -} - -func gnmiClient(dut *ondatra.DUTDevice, gnmiAddr string) (gpb.GNMIClient, error) { - conn, err := grpc.NewClient( - gnmiAddr, - grpc.WithTransportCredentials( - credentials.NewTLS(&tls.Config{ - InsecureSkipVerify: true, // NOLINT - })), - ) - if err != nil { - return nil, fmt.Errorf("grpc.NewClient => unexpected failure dialing GNMI (should not require auth): %w", err) - } - return gpb.NewGNMIClient(conn), nil -} - // helper function for native model; // Configure a new user by passing a username and password and assign that user to a role // ensure role has write access @@ -165,24 +137,6 @@ func createNativeUser(t testing.TB, dut *ondatra.DUTDevice, user string, pass st func TestAuthentication(t *testing.T) { dut := ondatra.DUT(t, "dut") - var servDUT interface { - Service(string) (*tpb.Service, error) - } - if err := binding.DUTAs(dut.RawAPIs().BindingDUT(), &servDUT); err != nil { - t.Fatalf("DUT does not support Service function: %v", err) - } - sshService, err := servDUT.Service("ssh") - if err != nil { - t.Fatal(err) - } - sshAddr := fmt.Sprintf("%s:%d", sshService.GetOutsideIp(), sshService.GetOutside()) - gnmiService, err := servDUT.Service("gnmi") - if err != nil { - t.Fatal(err) - } - // Deliberately try to reach gnmi via the DUT (SSH) IP. - gnmiAddr := fmt.Sprintf("%s:%d", sshService.GetOutsideIp(), gnmiService.GetOutside()) - if deviations.SetNativeUser(dut) { createNativeUser(t, dut, "alice", "password", "admin") } else { @@ -216,17 +170,19 @@ func TestAuthentication(t *testing.T) { for _, tc := range tests { t.Run(tc.desc, func(t *testing.T) { t.Log("Trying SSH credentials") - sshClient, err := ssh.Dial("tcp", sshAddr, &ssh.ClientConfig{ - User: tc.user, - Auth: []ssh.AuthMethod{ - ssh.KeyboardInteractive(keyboardInteraction(tc.pass)), - ssh.Password(tc.pass), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), - }) - if err == nil { - defer sshClient.Close() + ctx, cancel := context.WithTimeout(t.Context(), 30*time.Second) + defer cancel() + client, err := credz.SSHWithPassword(ctx, dut, tc.user, tc.pass) + if tc.wantErr { + if err == nil { + t.Fatalf("Dialing ssh succeeded, but we expected to fail.") + } + return + } + if err != nil { + t.Fatalf("Failed dialing ssh, error: %s", err) } + defer client.Close() if tc.wantErr != (err != nil) { if tc.wantErr { t.Errorf("ssh.Dial got nil error, want error for user %q, password %q", tc.user, tc.pass) @@ -235,11 +191,11 @@ func TestAuthentication(t *testing.T) { } } - ctx := metadata.AppendToOutgoingContext( + ctx = metadata.AppendToOutgoingContext( context.Background(), "username", tc.user, "password", tc.pass) - gnmi, err := gnmiClient(dut, gnmiAddr) + gnmi, err := dut.RawAPIs().BindingDUT().DialGNMI(ctx) if err != nil { t.Fatal(err) } diff --git a/feature/system/attestz/tests/README.md b/feature/security/attestz/tests/enrollz_and_attestz/README.md similarity index 58% rename from feature/system/attestz/tests/README.md rename to feature/security/attestz/tests/enrollz_and_attestz/README.md index 169e35b6cfa..ddf80f22809 100644 --- a/feature/system/attestz/tests/README.md +++ b/feature/security/attestz/tests/enrollz_and_attestz/README.md @@ -1,4 +1,4 @@ -# attestz: General enrollz and attestz tests +# attestz-1: General enrollz and attestz tests ## Summary @@ -19,21 +19,21 @@ Finally, the tests should cover both initial install/bootstrapping, oIAK/oIDevID 2. The device successfully completed the bootz workflow where it obtained and applied all configurations/credentials/certificates and booted into the right OS image. 3. Device is serving `enrollz` and `attestz` gRPC endpoints. -### attestz-1: Validate attestz for initial install +### attestz-1.1: Validate attestz for initial install The test validates that the device completes TPM enrollment and attestation during initial device bootstrapping/install. | ID | Case | Result | | --- | ---- | ------ | -| attestz-1.1 | Successful enrollment and attestation | Device obtained oIAK and oIDevID certs, updated default SSL profile to rely on the oIDevID cert, and passed attestation for all control cards | -| attestz-1.2 | IAK/IDevID are not present on the device | `GetIakCert` fails with missing IAK/IDevID error | -| attestz-1.3 | Bad request for `GetIakCertRequest`, `RotateOIakCertRequest` and  `AttestRequest`. Examples: `ControlCardSelection control_card_selection` is not specified or `control_card_id.role = 0`. Invalid `control_card_id.serial` or `control_card_id.slot` | `GetIakCert`, `RotateOIakCert` and `Attest` fail with detailed invalid request error | -| attestz-1.4 | Store oIAK/oIDevId certs that have different underlying IAK/IDevID pub keys or intended for other control card | `RotateOIakCert` fails with detailed invalid request error | -| attestz-1.5 | `enrollz` workflow followed by a device reboot still results in a successful `attestz` workflow | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | -| attestz-1.6 | Full factory reset of the device after a successful `enrollz` workflow deletes oIAK and oIDevID certs, but does not affect IAK and IDevID certs | After factory reset the device fails to perform `attestz` workflow due to missing oIAK and oIDevID certs | -| attestz-1.7 | Out of bound or repeated `pcr_indices` in `AttestRequest` | `Attest` fails with detailed invalid request error | -| attestz-1.8 | RMA scenarios where an active control card ensures that a newly inserted standby control card completes TPM enrollment and attestation before obtaining **its own set** of owner-issued production credentials/certificates (no sharing of owner-issued production security artifacts is allowed between control cards) | `attestz` on a newly inserted control card fails before the card successfully completes TPM enrollment workflow; all RPCs relying on owner-issued credentials/certs fail on a newly inserted control card before the card successfully completes TPM enrollment and attestation workflows | -| attestz-1.9 | Regardless of which control card was active during `enrollz`, both control cards should be able to successfully complete `attestz` workflow as active control cards | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | +| attestz-1.1.1 | Successful enrollment and attestation | Device obtained oIAK and oIDevID certs, updated default SSL profile to rely on the oIDevID cert, and passed attestation for all control cards | +| attestz-1.1,2 | IAK/IDevID are not present on the device | `GetIakCert` fails with missing IAK/IDevID error | +| attestz-1.1.3 | Bad request for `GetIakCertRequest`, `RotateOIakCertRequest` and `AttestRequest`. Examples: `ControlCardSelection control_card_selection` is not specified or `control_card_id.role = 0`. Invalid `control_card_id.serial` or `control_card_id.slot` | `GetIakCert`, `RotateOIakCert` and `Attest` fail with detailed invalid request error | +| attestz-1.1.4 | Store oIAK/oIDevId certs that have different underlying IAK/IDevID pub keys or intended for other control card | `RotateOIakCert` fails with detailed invalid request error | +| attestz-1.1.5 | `enrollz` workflow followed by a device reboot still results in a successful `attestz` workflow | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | +| attestz-1.1.6 | Full factory reset of the device after a successful `enrollz` workflow deletes oIAK and oIDevID certs, but does not affect IAK and IDevID certs | After factory reset the device fails to perform `attestz` workflow due to missing oIAK and oIDevID certs | +| attestz-1.1.7 | Out of bound or repeated `pcr_indices` in `AttestRequest` | `Attest` fails with detailed invalid request error | +| attestz-1.1.8 | RMA scenarios where an active control card ensures that a newly inserted standby control card completes TPM enrollment and attestation before obtaining **its own set** of owner-issued production credentials/certificates (no sharing of owner-issued production security artifacts is allowed between control cards) | `attestz` on a newly inserted control card fails before the card successfully completes TPM enrollment workflow; all RPCs relying on owner-issued credentials/certs fail on a newly inserted control card before the card successfully completes TPM enrollment and attestation workflows | +| attestz-1.1.9 | Regardless of which control card was active during `enrollz`, both control cards should be able to successfully complete `attestz` workflow as active control cards | Device obtained oIAK and oIDevID certs and passed attestation for all control cards | 1. Call `GetIakCert` for an active control card with correct `ControlCardSelection`. 2. Verify that correct IDevID cert was used for establishing TLS session: @@ -64,15 +64,15 @@ The test validates that the device completes TPM enrollment and attestation duri 18. Verify that the oIDevID cert of active control card was used for establishing TLS session and verify that oIDevID cert of standby control card was specified in the response payload. 19. Repeat steps (12-16) for the standby control card. -### attestz-2: Validate oIAK and oIDevID rotation +### attestz-1.2: Validate oIAK and oIDevID rotation The test validates that the device can rotate oIAK and oIDevID certificates post-install. | ID | Case | Result | | ----------- | ----------------| ------ | -| attestz-2.1 | Successful oIAK and oIDevID cert rotation when no owner-issued mTLS cert is available on the device | Device obtained newly-rotated oIAK and oIDevID certs and passed attestation for all control cards relying on the new oIAK and oIDevID certs | -| attestz-2.2 | Successful oIAK and oIDevID cert rotation when owner-issued mTLS cert is available on the device | Device obtained newly-rotated oIAK and oIDevID certs and passed attestation for all control cards relying on the new oIAK and previously owner-issued mTLS cert | -| attestz-2.3 | Device is unable to authenticate switch owner (e.g. no suitable TLS trust bundle) during oIAK/oIDevID rotation | Both `GetIakCert` and `RotateOIakCert` return authentication failure error | +| attestz-1.2.1 | Successful oIAK and oIDevID cert rotation when no owner-issued mTLS cert is available on the device | Device obtained newly-rotated oIAK and oIDevID certs and passed attestation for all control cards relying on the new oIAK and oIDevID certs | +| attestz-1.2.2 | Successful oIAK and oIDevID cert rotation when owner-issued mTLS cert is available on the device | Device obtained newly-rotated oIAK and oIDevID certs and passed attestation for all control cards relying on the new oIAK and previously owner-issued mTLS cert | +| attestz-1.2.3 | Device is unable to authenticate switch owner (e.g. no suitable TLS trust bundle) during oIAK/oIDevID rotation | Both `GetIakCert` and `RotateOIakCert` return authentication failure error | 1. Execute "Initial Install" workflow. 2. Issue new oIAK and oIDevID certs for active control card, call `RotateOIakCert` to store those on the right card and verify successful response. @@ -80,18 +80,28 @@ The test validates that the device can rotate oIAK and oIDevID certificates post 4. Call `Attest` for active and standby control cards and ensure they use the latest oIAK for attestation and, if there is no owner-provisioned TLS cert installed, use latest oIDevID for TLS session. 5. Do the same verification of attestation responses as in "Initial Install" workflow. -### attestz-3: Validate post-install re-attestation +### attestz-1.3: Validate post-install re-attestation The test validates that the device completes TPM attestation after initial bootstrapping when the device is already handling production traffic and has already been provisioned with oIAK cert and owner-issued mTLS credentials/certs to communicate with owner infrastructure. | ID | Case | Result | | ----------- | ----------------| ------ | -| attestz-3.1 | Successful post-install re-attestation relying an owner-issued mTLS cert | Device passed attestation for all control cards relying on the latest oIAK and mTLS certs | -| attestz-3.2 | Two re-attestations separated by a device reboot result in the same PCR values, but different PCR Quote (due a different random nonce in `AttestRequest`) | Device passed multiple re-attestations separated by a reboot for all control cards relying on the latest oIAK and mTLS certs | -| attestz-3.2 | When an active control card becomes unavailable, standby control card becomes active and can successfully complete re-attestation | Standby control card passed re-attestation after an active control card failure, relying on the latest oIAK and mTLS certs| -| attestz-3.3 | Device is unable to authenticate switch owner (e.g. no suitable TLS trust bundle) during attestation | `Attest` returns authentication failure error | +| attestz-1.3.1 | Successful post-install re-attestation relying an owner-issued mTLS cert | Device passed attestation for all control cards relying on the latest oIAK and mTLS certs | +| attestz-1.3.2 | Two re-attestations separated by a device reboot result in the same PCR values, but different PCR Quote (due a different random nonce in `AttestRequest`) | Device passed multiple re-attestations separated by a reboot for all control cards relying on the latest oIAK and mTLS certs | +| attestz-1.3.2 | When an active control card becomes unavailable, standby control card becomes active and can successfully complete re-attestation | Standby control card passed re-attestation after an active control card failure, relying on the latest oIAK and mTLS certs| +| attestz-1.3.3 | Device is unable to authenticate switch owner (e.g. no suitable TLS trust bundle) during attestation | `Attest` returns authentication failure error | 1. Execute "Initial Install" workflow. 2. Provision the device with switch owner mTLS credentials (separate key pair and cert for each control card). 3. Call `Attest` for active and standby control cards and ensure they use the new mTLS cert for TLS connection and the latest oIAK for attestation. 4. Do the same verification of attestation responses as in "Initial Install" workflow. + +## OpenConfig Path and RPC Coverage + +TODO: Update the Paths and RPCs used. + +```yaml +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/feature/security/attestz/tests/enrollz_and_attestz/metadata.textproto b/feature/security/attestz/tests/enrollz_and_attestz/metadata.textproto new file mode 100644 index 00000000000..a9d87b6f88a --- /dev/null +++ b/feature/security/attestz/tests/enrollz_and_attestz/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f0d32dbd-3099-40b9-ba95-9c7205c8b7d2" +plan_id: "attestz-1" +description: "General enrollz and attestz tests" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/security/gnsi/acctz/feature.textproto b/feature/security/gnsi/acctz/feature.textproto deleted file mode 100644 index 76c8f93c5c5..00000000000 --- a/feature/security/gnsi/acctz/feature.textproto +++ /dev/null @@ -1,7 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "security_gnsi_acctz" - version: 0 -} diff --git a/feature/security/gnsi/authz/feature.textproto b/feature/security/gnsi/authz/feature.textproto deleted file mode 100644 index 183c61709fa..00000000000 --- a/feature/security/gnsi/authz/feature.textproto +++ /dev/null @@ -1,44 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "security_gnsi_authz" - version: 1 -} - -# TODO: move the https://github.com/openconfig/gnsi/blob/main/authz/gnsi-authz.yang to the OC main. -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/state/authz-policy-version" -# } -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/state/authz-policy-created-on" -# } -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/name" -# } -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/access-accepts" -# } -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/access-rejects" -# } -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/last-access-accept" -# } -# telemetry_path { -# path: "/system/grpc-servers/grpc-server/authz-policy-counters/rpcs/rpc/rpc[name]/state/last-access-reject" -# } \ No newline at end of file diff --git a/feature/security/gnsi/certz/feature.textproto b/feature/security/gnsi/certz/feature.textproto deleted file mode 100644 index e12f9bcbf7d..00000000000 --- a/feature/security/gnsi/certz/feature.textproto +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "security_gnsi_certz" - version: 1 -} - -# TODO: Determine what YANG/Config/Telemetry paths are to be tested here. diff --git a/feature/security/gnsi/certz/trust_bundle/README.md b/feature/security/gnsi/certz/trust_bundle/README.md deleted file mode 100644 index fbbee7d1e6d..00000000000 --- a/feature/security/gnsi/certz/trust_bundle/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# Trust Bundle - -## Summary - -Server and client TLS endpoints use x.509 certificates for -identification of the calling or called endpoint. Systems -could use self-signed certificates and not validate, but -this is an insecure practice. - -Servers and clients should require that the certificates -used are validated and are signed by a known Certificate -Authority(CA). - -The known CAs which can be used are contained in a -'trust bundle', which is a list of public keys of the CAs. -The list of CA public keys must be kept up to date, as -CAs will rotate their key material on a regular cadence. - -CA keys may be one of two valid (at this time) key algorithms: - - * RSA - * ECDSA - -(Note: Security of key algorithms is subject to change, the -system must be able to support more than one key type at any -point in time in order to support key algorithm change events.) - -A trust bundle may have one or more certificates contained in -it, systems should be able to support at least one thousand -CA keys in such a bundle. - - -## Baseline Setup - -### Input Args - - * the set of certificate testdata generated with the mk_cas.sh - script in the featureprofiles/feature/security/gnsi/certz/test_data - directory. - -### DUT service setup - -Configure the DUT to enable the following sevices (that are using gRPC) are -up and require using mTLS for authentication: - - * gNMI - * gNOI - * gNSI - * gRIBI - * P4RT - -For each trust_bundle created by mk_cas.sh, configure the -services to load the correct key-type certificate, key and -trust_bundle. - -## Tests - -### Test Certz-4.1 - -Load the server certificate and key from each of the following CA sets: - * ca-01 - * ca-02 - * ca-10 - * ca-1000 - * ca-20000 - -Each service must be configured to use the appropriate certificate and validate -that certificate using the included trust_bundle. - -Perform this test with both RSA dn ECDSA key-types. - -## 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. - -TODO(OCRPC): Record may not be correct or complete - -```yaml -rpcs: - gnsi: - certz.v1.Certz.GetProfileList: -``` - - -## Minimum DUT Platform Requirement - -vRX diff --git a/feature/security/gnsi/credentialz/feature.textproto b/feature/security/gnsi/credentialz/feature.textproto deleted file mode 100644 index 4bfd23a1c3f..00000000000 --- a/feature/security/gnsi/credentialz/feature.textproto +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "security_gnsi_credentialz" - version: 1 -} diff --git a/feature/security/gnsi/credentialz/tests/README.md b/feature/security/gnsi/credentialz/tests/README.md deleted file mode 100644 index a6cd29841d9..00000000000 --- a/feature/security/gnsi/credentialz/tests/README.md +++ /dev/null @@ -1,329 +0,0 @@ -# gNSI Credentialz Tests - -## Summary -Test gNSI Credentialz API behaviors. - -## Request Examples -These gNSI credentialz requests are examples that can be used with cases below. -======= - -### Configure a testuser and password - -``` -stream.Send( - RotateAccountCredentialsRequest { - password: PasswordRequest { - accounts: Account { - account: "testuser", - password: Password { - value: { - plaintext: "i$V5^6IhD*tZ#eg1G@v3xdVZrQwj", - } - }, - version: "v1.4", - created_on: 3214451134, - } - } - } -) - -resp := stream.Receive() -``` - -### Set the TrustedUserCaKeys - -Note: You will need to set the contents of the ssh_ca_public_keys message to the -contents of your CA public key file. - -``` -stream.Send( - RotateHostParametersRequest { - ssh_ca_public_key: CaPublicKeyRequest { - ssh_ca_public_keys: "A....=", - version: "v1.0", - created_on: 3214451134, - } - } -) - -resp := stream.Receive() -``` - -### Disallow passwords for SSH - -``` -stream.Send( - RotateHostParametersRequest { - authentication_allowed: AllowedAuthenticationRequest { - authentication_types: AuthenticationType { - AuthenticationType_PUBKEY.Enum(), - } - } - } -) -``` - -### Configure and enable GLOME - -``` -stream.Send( - RotateHostParametersRequest { - enabled: true, - key: "4242424242424242424242424242424242424242424242", - key_version: 4, - url_prefix: "https://example.invalid", - } -) -``` - -### Populate Authorized Principals - -``` -stream.Send( - RotateAccountCredentialsRequest { - user: AuthorizedUsersRequest { - policies: UserPolicy { - account: "testuser", - authorized_principals: SshAuthorizedPrincipal { - authorized_user: "principal_name", - }, - version: "v1.4", - created_on: 3214451134, - } - } - } -) - -resp := stream.Receive() -``` - -### Populate Authorized Principals -Note: Key contents must be the public key from your generated user keypair. - -``` -stream.Send( - RotateAccountCredentialsRequest { - credential: AuthorizedKeysRequest { - credentials: AccountCredentials { - account: "testuser", - authorized_keys: AuthorizedKey { - authorized_key: "A....=", - }, - version: "v1.4", - created_on: 3214451134, - } - } - } -) - -resp := stream.Receive() -``` - -### Set the Host Certificate - -Note: You will need to set the contents of the host certificate generated from -your test setup. - -``` -stream.Send( - RotateHostParametersRequest { - server_keys: ServerKeysRequest { - auth_artifacts: []AuthenticationArtifacts{ - certificate: []bytes("...."), - }, - version: "v1.0", - created_on: 3214451134, - } - } -) - -resp := stream.Receive() -``` - -## Tests - -### Credentialz-1, Password console login - -#### Setup - -* Set a username of `testuser` using gnsi.Credentialz -* Set a password of `i$V5^6IhD*tZ#eg1G@v3xdVZrQwj` (see RotateAccountCredentials, PasswordRequest, plaintext) -* Connect to the console - - -#### Pass case -* Provide correct username/password on console. - * Authentication must result in success with a prompt. - * There must be accounting for the login which includes the `testuser` - * Ensure telemetry values for password-version and password-created-on match the - values from our `RotateAccountCredentialsRequest` for - `/oc-sys:system/oc-sys:aaa/oc-sys:authentication/oc-sys:users/oc-sys:user/oc-sys:state:password-version` -and -`/oc-sys:system/oc-sys:aaa/oc-sys:authentication/oc-sys:users/oc-sys:user/oc-sys:state:password-created-on` - -#### Fail case 1 -* Provide incorrect username and correct password. - * Authentication must fail. - -#### Fail case 2 -* Provide incorrect password, but correct username. - * Authentication must fail. - -### Credentialz-2, SSH password login disallowed - -#### Setup -* Set a username of `testuser` -* Set a password of `i$V5^6IhD*tZ#eg1G@v3xdVZrQwj` (see - RotateAccountCredentials, PasswordRequest, plaintext) -* Ensure that AAA (TACACS+/Radius) authentication is not configured. -* Create a ssh CA keypair with `ssh-keygen -f /tmp/ca`. -* Create a user keypair with `ssh-keygen -t ed25519` -* Sign the user public key into a certificate using the CA using `ssh-keygen -s - /tmp/ca -I testuser -n principal_name -V +52w ~/.ssh/id_ed25519.pub`. You will - find your certificate in `~/.ssh` ending in `-cert.pub`. -* Set the device TrustedUserCAKeys (see RotateHostParameters - ssh_ca_public_key) with the CA public key, found in `/tmp/ca.pub`. -* Set authentication types to only permit PUBKEY (see - AllowedAuthenticationRequest). -* Set authorized_users for `testuser` with a principal of `principal_name`. - -#### Pass case -* Attempt an ssh authentication using the username (ssh testuser@DUT) and password. - * Authentication must fail. - * Ensure that access failure telemetry counters are incremented - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-rejects` - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-reject` -* Attempt password authentication on the console. - * Authentication must result in success with a prompt. - * Ensure that access accept telemetry counters are incremented - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` -* Attempt password authentication on the console. - * Authentication must result in success with a prompt. -* Attempt certificate authentication over ssh, `ssh testuser@DUT`. - * Use the ssh user certificate with a signature verifiable by a - TrustedUserCAKey public key created above. - * Authentication must succeed. - * Ensure that access accept telemetry counters are incremented - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` - * Accounting, using gnsi Accounting must set the identity string (see - acct.proto AuthDetail message) to equal the principal (principal_name) - from the certificate rather than the system role (testuser). - -### Credentialz-3, Host Certificate - -#### Setup -* Create a ssh CA keypair with `ssh-keygen -f /tmp/ca`. -* Fetch the ssh server's host public key. -* Sign the public key from the previous step into a host certificate using the - CA key `ssh-keygen -s /tmp/ca -I dut -h -n dut.example.invalid -V +52w - /location/of/host/public_key.pub` -* Add the certificate to the server (see RotateHostParameters, - AuthenticationArtifacts, certificate) - -#### Pass case -* ssh to the server. - * You must receive the host certificate signed by your CA. - * Ensure telemetry values for version and created-on match the values set by - RotateHostParameters for -`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:active-host-certificate-version` -and -`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:active-host-certificate-created-on` - -### Credentialz-4, SSH Public Key Authentication - -#### Setup -* Create a user ssh CA keypair with `ssh-keygen`. No arguments are required and - the keys will be put in `~/.ssh/`. -* Create a username on the ssh server and add the public key (see - RotateAccountCredentials AuthorizedKeysRequest). - -#### Pass case -* Attempt to ssh into the server with the username, presenting the ssh key. - * Authentication must succeed. - * Ensure telemetry values for version and created-on match the values set by - RotateHostParameters for -`/oc-sys:system/oc-sys:aaa/oc-sys:authentication/oc-sys:users/oc-sys:user/oc-sys:state:authorized-keys-list-version` -and -`/oc-sys:system/oc-sys:aaa/oc-sys:authentication/oc-sys:users/oc-sys:user/oc-sys:state:authorized-keys-list-created-on` - * Ensure that access accept telemetry counters are incremented - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` - -#### Fail case -* Remove the user ssh key (by sending an AuthorizedKeysRequest with no - authorized_keys message. -* Attempt to ssh into the server with the username. -* Public key authentication should fail. - -### Credentialz-5, HIBA Authentication - -#### Setup -* Set a username of "testuser" -* Follow the instructions for setting up a [HIBA - CA](https://github.com/google/hiba/blob/main/CA.md) -* Sign the user public key into a certificate using the CA with a "shell" grant. -* Set up the device with a TrustedUserCAKey (see RotateHostParameters - ssh_ca_public_key) with the CA public key. -* Set authentication types to only permit PUBKEY (see - AllowedAuthenticationRequest). -* Set the AuthorizedPrincipalsCommand by setting the tool to `TOOL_HIBA_DEFAULT` - (see RotateHostParameters, AuthorizedPrincipalCheckRequest) - -#### Pass case -* Log into the server as "testuser" using the client certificate. - * Authentication must succeed. - * Accounting, using gnsi Accounting must set the identity string (see - acct.proto AuthDetail message) to equal the principal (principal_name) - from the certificate rather than the system role (testuser). - * Ensure telemetry values for version and created-on match the values set by - RotateHostParameters for -`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:active-host-certificate-version` -and -`/oc-sys:system/oc-sys:ssh-server/oc-sys:state:active-host-certificate-created-on` - * Ensure that access accept telemetry counters are incremented - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-accepts` - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:last-access-accept` - -#### Fail case -* Remove the certificate with the HIBA grant (or wait for expiration) -* Create an ssh certificate with no grants. -* Log into the server as "testuser" with this certificate - * Authentication must fail - * Ensure that access rejects telemetry counter is incremented - `/oc-sys:system/oc-sys:ssh-server/oc-sys:state:counters:access-rejects` - -### Credentialz-6, GLOME Configuration - -#### Setup -* Create a glome key with `glome` following [these - instructions](https://github.com/google/glome?tab=readme-ov-file#getting-started). -* Send a RotateHostParameters GlomeRequest message, with key, key_version, and - prefix_url. - -#### Pass case -* Attempt a console connection. - * Prompt must include a GLOME challenge. - * Use the `glome` binary along with your generated key to generate an - authorization code. - * Use the authorization code at the console prompt. - * Authorization must succeed. - * Ensure telemetry values for version and enabled match what was set in Setup. - -#### Fail case -* Attempt a console connection. - * Enter `fake-authorization-code` in the prompt. - * Authentication must fail. -======= -## 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. - -TODO(OCRPC): Record may not be complete - -```yaml -rpcs: - gnsi: - credentialz.v1.Credentialz.RotateAccountCredentials: -``` diff --git a/feature/sflow/feature.textproto b/feature/sflow/feature.textproto deleted file mode 100644 index e253000ce58..00000000000 --- a/feature/sflow/feature.textproto +++ /dev/null @@ -1,99 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "sflow" - version: 1 -} - -# Global -config_path { - path: "/sampling/sflow/config/agent-id-ipv4" -} -config_path { - path: "/sampling/sflow/config/agent-id-ipv6" -} -config_path { - path: "/sampling/sflow/config/enabled" -} -config_path { - path: "/sampling/sflow/config/egress-sampling-rate" -} -config_path { - path: "/sampling/sflow/config/ingress-sampling-rate" -} -config_path { - path: "/sampling/sflow/config/sample-size" -} -telemetry_path { - path: "/sampling/sflow/state/agent-id-ipv4" -} -telemetry_path { - path: "/sampling/sflow/state/agent-id-ipv6" -} -telemetry_path { - path: "/sampling/sflow/state/enabled" -} -telemetry_path { - path: "/sampling/sflow/state/egress-sampling-rate" -} -telemetry_path { - path: "/sampling/sflow/state/ingress-sampling-rate" -} -telemetry_path { - path: "/sampling/sflow/state/sample-size" -} - -# Interfaces -config_path { - path: "/sampling/sflow/interfaces/interface/config/enabled" -} -config_path { - path: "/sampling/sflow/interfaces/interface/config/name" -} -telemetry_path { - path: "/sampling/sflow/interfaces/interface/state/enabled" -} -telemetry_path { - path: "/sampling/sflow/interfaces/interface/state/name" -} - -# Collectors -config_path { - path: "/sampling/sflow/collectors/collector/config/address" -} -config_path { - path: "/sampling/sflow/collectors/collector/config/network-instance" -} -config_path { - path: "/sampling/sflow/collectors/collector/config/port" -} -config_path { - path: "/sampling/sflow/collectors/collector/config/source-address" -} -telemetry_path { - path: "/sampling/sflow/collectors/collector/state/address" -} -telemetry_path { - path: "/sampling/sflow/collectors/collector/state/network-instance" -} -telemetry_path { - path: "/sampling/sflow/collectors/collector/state/port" -} -telemetry_path { - path: "/sampling/sflow/collectors/collector/state/source-address" -} diff --git a/feature/sflow/otg_tests/sflow_base_test/metadata.textproto b/feature/sflow/otg_tests/sflow_base_test/metadata.textproto index 505ac3cb1f7..1be910c23ad 100644 --- a/feature/sflow/otg_tests/sflow_base_test/metadata.textproto +++ b/feature/sflow/otg_tests/sflow_base_test/metadata.textproto @@ -11,6 +11,7 @@ platform_exceptions: { } deviations: { ipv4_missing_enabled: true + sflow_ingress_min_sampling_rate: 262144 } } platform_exceptions: { @@ -33,5 +34,6 @@ platform_exceptions: { default_network_instance: "default" static_protocol_name: "STATIC" sflow_source_address_update_unsupported: true + fr_breakout_fix: true } } diff --git a/feature/sflow/otg_tests/sflow_base_test/sflow_base_test.go b/feature/sflow/otg_tests/sflow_base_test/sflow_base_test.go index d909410ce94..e9d90353162 100644 --- a/feature/sflow/otg_tests/sflow_base_test/sflow_base_test.go +++ b/feature/sflow/otg_tests/sflow_base_test/sflow_base_test.go @@ -151,15 +151,23 @@ func configureDUTBaseline(t *testing.T, dut *ondatra.DUTDevice) { d := gnmi.OC() p1 := dut.Port(t, "port1") - gnmi.Replace(t, dut, d.Interface(p1.Name()).Config(), dutSrc.NewOCInterface(p1.Name(), dut)) - p2 := dut.Port(t, "port2") - gnmi.Replace(t, dut, d.Interface(p2.Name()).Config(), dutDst.NewOCInterface(p2.Name(), dut)) - if deviations.ExplicitPortSpeed(dut) { - fptest.SetPortSpeed(t, p1) - fptest.SetPortSpeed(t, p2) + dutPortAttrs := map[*ondatra.Port]*attrs.Attributes{ + p1: dutSrc, + p2: dutDst, } + + for dutPort, dutPortAttr := range dutPortAttrs { + dutInt := dutPortAttr.NewOCInterface(dutPort.Name(), dut) + if deviations.FrBreakoutFix(dut) && dutPort.PMD() == ondatra.PMD100GBASEFR { + dutInt.GetOrCreateEthernet().SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB) + dutInt.GetOrCreateEthernet().SetDuplexMode(oc.Ethernet_DuplexMode_FULL) + dutInt.GetOrCreateEthernet().SetAutoNegotiate(false) + } + gnmi.Replace(t, dut, d.Interface(dutPort.Name()).Config(), dutInt) + } + if deviations.ExplicitInterfaceInDefaultVRF(dut) { fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) diff --git a/feature/static/otg_tests/next_hop_encap_udp/README.md b/feature/static/otg_tests/next_hop_encap_udp/README.md new file mode 100644 index 00000000000..d20ccb65b80 --- /dev/null +++ b/feature/static/otg_tests/next_hop_encap_udp/README.md @@ -0,0 +1,339 @@ +# RT-3.53: Static route based GUE Encapsulation to IPv4 tunnel + +## Summary + +This test verifies using static route to encapsulate packets in an +IPv4 GUE tunnel. The encapsulation is based on a statically configured GUE +tunnel. + +The GUE header ((UDPoIP)) used in this test refers to GUE Variant 1 as specified +in +[draft-ietf-intarea-gue-09](https://datatracker.ietf.org/doc/html/draft-ietf-intarea-gue-09). + +The following behavioral properties are called out for awareness: + +* When the DSCP for the tunnel is not explicitly set, it is copied from the + inner to the outer encapsulating header. +* When the TTL for the tunnel is not explicitly set, it is decremented by 1 + and copied from inner to the outer encapsulating header. Except TTL=1, where + Time Exceeded is sent back to the inner source IP. + +## Testbed Type + +* [`featureprofiles/topologies/atedut_5.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_5.testbed) + +## Procedure + +### Test environment setup + + ``` + ------- + | | ==== LAG1 ==== [ ATE:Port2, ATE:Port3 ] + [ ATE:Port1 ] ---- | DUT | + | | ==== LAG2 ==== [ ATE:Port4, ATE:Port5 ] + ------- + ``` + +* Routes are advertised from ATE:Port1 +* Traffic is generated from ATE:Port1 +* ATE:Port2, ATE:Port3, ATE:Port4 and ATE:Port5 are used as the destination port for GUE encapsulated traffic +* PNH-IPv6 is fc00:10::1 +* IPv4-DST-GUE is 10.50.50.1 +* IPv4-DST-NET is 10.1.1.1 +* IPv4-DST-NET is 2001:DB8::10:1:1::1 + + +#### Configuration + +1. DUT:Port1 is configured as singleton interface towards ATE:Port1. + + - DUT:Port1:IPv6 is 2001:DB8::192:168:10:2/126 + - ATE:Port1:IPv6 is 2001:DB8::192:168:10:1/126 + +2. DUT:Port2 and DUT:Port3 are configured as LAG1 interface towards + ATE:Port2 and ATE:Port3 respectively. + + - DUT:LAG1:IPv4 is 192.168.20.2/30 + - ATE:LAG1:IPv4 is 192.168.20.2/30 + +3. DUT:Port4 and DUT:Port5 are configured as LAG2 interface towards + ATE:Port4 and ATE:Port5 respectively. + - DUT:LAG1:IPv4 is 192.168.30.2/30 + - ATE:LAG1:IPv4 is 192.168.30.2/30 + +4. DUT:Port1 is configured to form one iBGP session with ATE:Port1 using + [RFC5549](https://datatracker.ietf.org/doc/html/rfc5549). + Peering is done with the directly connected interface IP. + +5. ATE:Port1 is configured to advertise destination networks + IPv4-DST-NET/32 and IPv6-DST-NET/128 via BGP to DUT. The protocol + next hop of both the IPv4 and IPv6 networks should be PNH-IPv6. + +6. DUT is configured with an IPv4 GUE tunnel with destination + IPv4-DST-GUE without any explicit tunnel Type of Service (ToS) or + Time to Live (TTL) values. The next hop group will be used for + GUE encapsulation. + +7. DUT is configured with the following static routes: + + - To PNH-IPv6, next hop is the statically configured IPv4 GUE tunnel. + - To IPv4-DST-GUE, next hop is ATE:LAG1:IPv4. + - To IPv4-DST-GUE, next hop is ATE:LAG2:IPv4. + + +### RT-3.53.1: IPv4 traffic GUE encapsulation without explicit ToS/TTL configuration on tunnel + +ATE action: + +* Generate 12000000 **IPv4 packets** from ATE:Port1 to random IP addresses in + IPv4-DST-NET ensuring that there are at least 65000 different 5-tuple flows. + * Use 512 bytes frame size. + * Set ToS value of *0x80* for all packets. + * Set TTL of all packets to *10*. + +Verify: + +* The packet count of traffic sent from ATE:Port1 should be equal to the sum + of all packets: + * Egressing DUT:Port2, DUT:Port3, DUT:Port4 and DUT:Port5 + by checking `out-unicast-pkts` counter. + * Received on ATE:Port2, ATE:Port3, ATE:Port4 and ATE:Port5. +* All packets received on ATE:Port2, ATE:Port3, ATE:Port4 and ATE:Port5 are + GUE encapsulated. +* ECMP hashing works over the two LAG interfaces with a tolerance of 6%. +* LAG hashing works over the two singleton ports within LAG1 and LAG2 with a + tolerance of 6%. +* ToS for all GUE encapsulated packets received at ATE ports: + * GUE header ToS is **0x80**. + * Inner header ToS is **0x80**. +* TTL for all GUE encapsulated packets received at ATE ports: + * GUE header TTL is **9**. + * Inner header TTL is **9**. + +### RT-3.53.2: IPv6 traffic GUE encapsulation without explicit ToS/TTL configuration on tunnel + +* Modify the flows in `RT-3.53.1` to use IPv6 destination IPv6-DST-NET and + repeat the traffic generation and validation. + +### RT-3.53.3: IPv4 traffic GUE encapsulation with explicit ToS configuration on tunnel + +DUT and ATE actions: + +* Re-configure the IPv4 GUE tunnel on the DUT with ToS value *0x60*. +* Generate the same flows in `RT-3.53.1`. + +Verify: + +* Repeat same verifications in `RT-3.53.1` but with the following differences + * ToS for all GUE encapsulated packets: + * GUE header ToS is **0x60**. + +### RT-3.53.4: IPv6 traffic GUE encapsulation with explicit ToS configuration on tunnel + +* Modify the flows in `RT-3.53.3` to use IPv6 destination IPv6-DST-NET and + repeat the traffic generation and validation. + +### RT-3.53.5: IPv4 traffic GUE encapsulation with explicit TTL configuration on tunnel + +DUT and ATE actions: + +* Re-configure the IPv4 GUE tunnel on the DUT without an explicit ToS value. +* Re-configure the IPv4 GUE tunnel on the DUT with TTL value of *20*. +* Generate the same flows in `RT-3.53.1`. + +Verify: + +* Repeat same verifications in `RT-3.53.1` but with the following differences + * TTL for all GUE encapsulated packets: + * GUE header TTL is **20**. + +### RT-3.53.6: IPv6 traffic GUE encapsulation with explicit TTL configuration on tunnel + +* Modify the flows in `RT-3.53.5` to use IPv6 destination IPv6-DST-NET and + repeat the traffic generation and validation. + +### RT-3.53.7: IPv4 traffic GUE encapsulation with explicit ToS and TTL configuration on tunnel + +DUT and ATE actions: + +* Re-configure the IPv4 GUE tunnel on the DUT with ToS value *0x60*. +* Re-configure the IPv4 GUE tunnel on the DUT with TTL value of *20*. +* Generate the same flows in `RT-3.53.1`. + +Verify: + +* Repeat same verifications in `RT-3.53.1` but with the following differences + * ToS for all GUE encapsulated packets: + * GUE header ToS is **0x60**. + * TTL for all GUE encapsulated packets: + * GUE header TTL is **20**. + +### RT-3.53.8: IPv6 traffic GUE encapsulation with explicit ToS and TTL configuration on tunnel + +* Modify the flows in `RT-3.53.7` to use IPv6 destination IPv6-DST-NET and + repeat the traffic generation and validation. + +### RT-3.53.9: IPv4 traffic GUE encapsulation to a single 5-tuple tunnel + +DUT and ATE actions: + +* Re-configure DUT without explicit ToS/TTL configuration on tunnel +* Modify flows in `RT-3.53.1` to be a single flow only; use fixed source and + destination IPs as well as fixed source and destination UDP ports. + +Verify: + +Verify: + +* Repeat same verifications in `RT-3.53.1` but with the following differences + * All traffic is hashed to a only one LAG and only one singleton port in the + LAG. + +### RT-3.53.10: IPv4 traffic GUE encapsulation to a single tunnel + +* Modify the flows in `RT-3.53.9` to use IPv6 destination IPv6-DST-NET + and repeat the traffic generation and validation. + +### RT-3.53.11: IPv4 traffic that should be GUE encapsulated but TTL=1 + +ATE action: + +* Modify flows in `RT-3.53.1` and set TTL of all packets to *1*. + +Verify: + +* All packets should have TTL decremented to 0, dropped and ICMP Time + Exceeded (Type 11) / Time to Live exceeded in Transit (Code 0) sent back to + source address (ATE:Port1). + +### RT-3.53.12: IPv6 traffic that should be GUE encapsulated but Hop Limit=1 + +* Modify the flows in `RT-3.53.11` to use IPv6 destination IPv6-DST-NET + and repeat the traffic generation and validation. + +### Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "config": { + "identifier": "STATIC", + "name": "STATIC" + }, + "identifier": "STATIC", + "name": "STATIC", + "static-routes": { + "static": [ + { + "config": { + "prefix": "fc00:10::1/128" + }, + "next-hop-group": { + "config": { + "name": "ENCAP-NHG-1" + } + }, + "prefix": "fc00:10::1/128" + } + ] + } + } + ] + }, + "static": { + "next-hop-groups": { + "next-hop-group": [ + { + "config": { + "name": "ENCAP-NHG-1" + }, + "name": "ENCAP-NHG-1", + "next-hops": { + "next-hop": [ + { + "config": { + "index": "0" + }, + "index": "0" + } + ] + } + } + ] + }, + "next-hops": { + "next-hop": [ + { + "config": { + "index": "0" + }, + "encap-headers": { + "encap-header": [ + { + "config": { + "index": 0, + "type": "UDPV4" + }, + "index": 0, + "udp-v4": { + "config": { + "dscp": 32, + "dst-ip": "10.50.50.1", + "dst-udp-port": 6080, + "ip-ttl": 20, + "src-ip": "10.5.5.5", + "src-udp-port": 49152 + } + } + } + ] + }, + "index": "0" + } + ] + } + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # config + /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name: + /network-instances/network-instance/static/next-hop-groups/next-hop-group/next-hops/next-hop/config/index: + /network-instances/network-instance/static/next-hops/next-hop/config/index: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/config/index: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/config/type: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/dscp: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/dst-ip: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/dst-udp-port: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/ip-ttl: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/src-ip: + /network-instances/network-instance/static/next-hops/next-hop/encap-headers/encap-header/udp-v4/config/src-udp-port: + /network-instances/network-instance/protocols/protocol/static-routes/static/next-hop-group/config/name: + + # telemetry + /interfaces/interface/state/counters/out-unicast-pkts: + + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` diff --git a/feature/static/otg_tests/next_hop_encap_udp/metadata.textproto b/feature/static/otg_tests/next_hop_encap_udp/metadata.textproto new file mode 100644 index 00000000000..5c527f75071 --- /dev/null +++ b/feature/static/otg_tests/next_hop_encap_udp/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "1c0cc46d-4b13-4bd3-bfd5-cf0fc1c074ad" +plan_id: "RT-3.53" +description: "Static route based GUE Encapsulation to IPv4 tunnel" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/staticroute/feature.textproto b/feature/staticroute/feature.textproto deleted file mode 100644 index 9ee16d1323e..00000000000 --- a/feature/staticroute/feature.textproto +++ /dev/null @@ -1,36 +0,0 @@ -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "staticroute" - version: 1 -} - -config_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/config/prefix" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/index" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop" -} -config_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/interface-ref/config/interface" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/state/prefix" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/index" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/state/next-hop" -} -telemetry_path { - path: "/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/interface-ref/state/interface" -} -feature_profile_dependency { - name: "networkinstance" - version: 1 -} diff --git a/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go b/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go index f5defe9452d..7f8178d30a4 100644 --- a/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/basic_static_route_support_test.go @@ -54,6 +54,7 @@ const ( var ( dutPort1 = attrs.Attributes{ Desc: "dutPort1", + Name: "port1", IPv4: "192.0.2.1", IPv4Len: ipv4PrefixLen, IPv6: "2001:db8::192:0:2:1", @@ -71,6 +72,7 @@ var ( dutPort2 = attrs.Attributes{ Desc: "dutPort2", + Name: "port2", IPv4: "192.0.2.5", IPv4Len: ipv4PrefixLen, IPv6: "2001:db8::192:0:2:5", @@ -88,6 +90,7 @@ var ( dutPort3 = attrs.Attributes{ Desc: "dutPort3", + Name: "port3", IPv4: "192.0.2.9", IPv4Len: ipv4PrefixLen, IPv6: "2001:db8::192:0:2:9", @@ -105,6 +108,7 @@ var ( dutPort4 = attrs.Attributes{ Desc: "dutPort4", + Name: "port4", IPv4: "192.0.2.13", IPv4Len: ipv4PrefixLen, IPv6: "2001:db8::192:0:2:d", @@ -245,8 +249,8 @@ func TestStaticRouteAddRemove(t *testing.T) { NetworkInstance: deviations.DefaultNetworkInstance(dut), Prefix: prefix.cidr(t), NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ - "0": oc.UnionString(atePort2.IPv4), - "1": oc.UnionString(atePort3.IPv4), + "0": oc.UnionString(atePort1.IPv4), + "1": oc.UnionString(atePort2.IPv4), }, } if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { @@ -279,8 +283,8 @@ func TestStaticRouteAddRemove(t *testing.T) { NetworkInstance: deviations.DefaultNetworkInstance(dut), Prefix: prefix.cidr(t), NextHops: map[string]oc.NetworkInstance_Protocol_Static_NextHop_NextHop_Union{ - "0": oc.UnionString(atePort2.IPv4), - "1": oc.UnionString(atePort3.IPv4), + "0": oc.UnionString(atePort1.IPv4), + "1": oc.UnionString(atePort2.IPv4), }, } if _, err := cfgplugins.NewStaticRouteCfg(b, sV4, dut); err != nil { @@ -1308,41 +1312,23 @@ func (td *testData) awaitISISAdjacency(t *testing.T, p *ondatra.Port, isisName s func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { t.Helper() - p1 := dut.Port(t, "port1") - p2 := dut.Port(t, "port2") - p3 := dut.Port(t, "port3") - p4 := dut.Port(t, "port4") - b := &gnmi.SetBatch{} - i1 := dutPort1.NewOCInterface(p1.Name(), dut) - i2 := dutPort2.NewOCInterface(p2.Name(), dut) - i3 := dutPort3.NewOCInterface(p3.Name(), dut) - i4 := dutPort4.NewOCInterface(p4.Name(), dut) - if deviations.IPv6StaticRouteWithIPv4NextHopRequiresStaticARP(dut) { - i1.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateNeighbor(dummyV6).LinkLayerAddress = ygot.String(dummyMAC) - i2.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateNeighbor(dummyV6).LinkLayerAddress = ygot.String(dummyMAC) - i3.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateNeighbor(dummyV6).LinkLayerAddress = ygot.String(dummyMAC) - i4.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateNeighbor(dummyV6).LinkLayerAddress = ygot.String(dummyMAC) - } - gnmi.BatchReplace(b, gnmi.OC().Interface(p1.Name()).Config(), i1) - gnmi.BatchReplace(b, gnmi.OC().Interface(p2.Name()).Config(), i2) - gnmi.BatchReplace(b, gnmi.OC().Interface(p3.Name()).Config(), i3) - gnmi.BatchReplace(b, gnmi.OC().Interface(p4.Name()).Config(), i4) - b.Set(t, dut) - - if deviations.ExplicitPortSpeed(dut) { - fptest.SetPortSpeed(t, p1) - fptest.SetPortSpeed(t, p2) - fptest.SetPortSpeed(t, p3) - fptest.SetPortSpeed(t, p4) - } - fptest.ConfigureDefaultNetworkInstance(t, dut) - - if deviations.ExplicitInterfaceInDefaultVRF(dut) { - fptest.AssignToNetworkInstance(t, dut, p1.Name(), deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, p2.Name(), deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, p3.Name(), deviations.DefaultNetworkInstance(dut), 0) - fptest.AssignToNetworkInstance(t, dut, p4.Name(), deviations.DefaultNetworkInstance(dut), 0) + for _, dutPorts := range []*attrs.Attributes{&dutPort1, &dutPort2, &dutPort3, &dutPort4} { + dutPort := dut.Port(t, dutPorts.Name) + dutInt := dutPorts.NewOCInterface(dutPort.Name(), dut) + if deviations.FrBreakoutFix(dut) && dutPort.PMD() == ondatra.PMD100GBASEFR { + ethPort := dutInt.GetOrCreateEthernet() + ethPort.SetAutoNegotiate(false) + ethPort.SetDuplexMode(oc.Ethernet_DuplexMode_FULL) + ethPort.SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB) + } + if deviations.IPv6StaticRouteWithIPv4NextHopRequiresStaticARP(dut) { + dutInt.GetOrCreateSubinterface(0).GetOrCreateIpv6().GetOrCreateNeighbor(dummyV6).LinkLayerAddress = ygot.String(dummyMAC) + } + gnmi.Replace(t, dut, gnmi.OC().Interface(dutPort.Name()).Config(), dutInt) + if deviations.ExplicitInterfaceInDefaultVRF(dut) { + fptest.AssignToNetworkInstance(t, dut, dutPort.Name(), deviations.DefaultNetworkInstance(dut), 0) + } } } diff --git a/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto b/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto index f0ad4706899..d2e4f90d8ca 100644 --- a/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto +++ b/feature/staticroute/otg_tests/basic_static_route_support_test/metadata.textproto @@ -19,6 +19,7 @@ platform_exceptions: { missing_isis_interface_afi_safi_enable: true isis_require_same_l1_metric_with_l2_metric: true unsupported_static_route_next_hop_recurse: true + fr_breakout_fix: true } } platform_exceptions: { diff --git a/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/README.md b/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/README.md new file mode 100644 index 00000000000..59547a8fd3d --- /dev/null +++ b/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/README.md @@ -0,0 +1,575 @@ +# RT-1.66: IPv4 Static Route with IPv6 Next-Hop + +## Summary +This test verified the functionality of IPv4 static route configured to redirect packets to a IPv6 destination. + +## Testbed type +[TESTBED_DUT_ATE_4 LINKS](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) + +## Procedure + +### Test environment setup: + * Connect DUT port-1, port-2, port-3 and port-4 to ATE port-1, port-2, port-3 and port-4 respectively + * Configure IPv4 addresses on port-1 of DUT '192.0.1.1/24' and ATE '192.0.1.2/24' + * Configure IPv4 addresses on port-2 of DUT '192.0.2.1/24' and ATE '192.0.2.2/24' + * Configure [IPv4, IPv6] addresses on port-3 of DUT ['192.0.3.1/24', '2001:db8:128:128::1/64'] and ATE '['192.0.3.2/24', 2001:db8:128:128::2/64'] + * Configure [IPv4, IPv6] addresses on port-4 of DUT ['192.0.4.1/24', '2001:db8:128:129::1/64'] and ATE '['192.0.4.2/24', 2001:db8:128:129::2/64'] + * Enable ECMP for static route + +### RT-1.66.1: IPv4 static route with an IPv6 next-hop in default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port3 '2001:db8:128:128::2' in a default network-instance + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must be received on ATE:port3 without any loss + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "2001:db8:128:128::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + +### RT-1.66.2: IPv4 static route with multiple IPv6 next-hop in default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port3 '2001:db8:128:128::2' in a default network-instance + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port4 '2001:db8:128:129::2' in a default network-instance + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must be equally received on ATE:port3 and ATE:port4 without any loss + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "2001:db8:128:128::2" + } + }, + { + "index": "1", + "config": { + "index": "1", + "next-hop": "2001:db8:128:129::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + + + +### RT-1.66.3: IPv4 static route with an IPv6 and an IPv4 next-hop in default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv4 next-hop of ATE:port3 '192.0.3.2/24' in a default network-instance + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port4 '2001:db8:128:129::2' in a default network-instance + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must be equally received on ATE:port3 and ATE:port4 without any loss + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "192.0.3.2" + } + }, + { + "index": "1", + "config": { + "index": "1", + "next-hop": "2001:db8:128:129::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + +### RT-1.66.4: IPv4 static route with an invalid IPv6 next-hop in default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port3 '2001:db8:128:130::2' in a default network-instance + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must not be received on ATE:port3 and there should be 100% traffic loss. + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "2001:db8:128:130::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + + +### RT-1.66.5: IPv4 static route with an IPv6 next-hop in non-default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port3 '2001:db8:128:128::2' in a non-default network-instance 'VRF1' + - Assosiate ATE:port1 and ATE:port3 with 'VRF1' + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must be received on ATE:port3 without any loss + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "VRF1", + "interfaces": { + "interface": [ + { + "id": "Ethernet1/1", + "config": { + "id": "Ethernet1/1" + } + }, + { + "id": "Ethernet1/3", + "config": { + "id": "Ethernet1/3" + } + } + ] + }, + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "2001:db8:128:128::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + +### RT-1.66.6: IPv4 static route with multiple IPv6 next-hop in non-default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port3 '2001:db8:128:128::2' in a non-default network-instance 'VRF1' + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port4 '2001:db8:128:129::2' in a non-default network-instance 'VRF1' + - Assosiate ATE:port1, ATE:port3 and ATE:port4 with 'VRF1' + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must be equally received on ATE:port3 and ATE:port4 without any loss + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "VRF1", + "interfaces": { + "interface": [ + { + "id": "Ethernet1/1", + "config": { + "id": "Ethernet1/1" + } + }, + { + "id": "Ethernet1/3", + "config": { + "id": "Ethernet1/3" + } + }, + { + "id": "Ethernet1/4", + "config": { + "id": "Ethernet1/4" + } + } + ] + }, + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "2001:db8:128:128::2" + } + }, + { + "index": "1", + "config": { + "index": "1", + "next-hop": "2001:db8:128:129::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + + + +### RT-1.66.7: IPv4 static route with an IPv6 and an IPv4 next-hop in non-default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv4 next-hop of ATE:port3 '192.0.3.2/24' in a non-default network-instance 'VRF1' + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port4 '2001:db8:128:129::2' in a non-default network-instance 'VRF1' + - Assosiate ATE:Port1, ATE:port3 and ATE:port4 with 'VRF1' + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must be equally received on ATE:port3 and ATE:port4 without any loss + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "VRF1", + "interfaces": { + "interface": [ + { + "id": "Ethernet1/1", + "config": { + "id": "Ethernet1/1" + } + }, + { + "id": "Ethernet1/3", + "config": { + "id": "Ethernet1/3" + } + }, + { + "id": "Ethernet1/4", + "config": { + "id": "Ethernet1/4" + } + } + ] + }, + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "192.0.3.2" + } + }, + { + "index": "1", + "config": { + "index": "1", + "next-hop": "2001:db8:128:129::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + +### RT-1.66.8: IPv4 static route with an invalid IPv6 next-hop in non-default network-instance + + * Step 1 - Generate DUT Configuration + - Configure a ipv4 static route '192.168.1.0/24' with IPv6 next-hop of ATE:port3 '2001:db8:128:130::2' in a non-default network-instance 'VRF1' + - Assosiate ATE:port1 and ATE:port3 with VRF1 + * Step 2 - Generate ATE Configuration + - Configure IPv4 traffic profile for source '192.0.1.2/24' and destination '192.168.1.0/24' with udp payload and random src/dest ports + * Step 3 - Traffic Test + - Start the traffic + - Monitor for 60 seconds + * Step 4 - Test Validations + - Traffic must not be received on ATE:port3 and there should be 100% traffic loss. + - Configuration must be accepted by device + +#### Canonical OC + +```json + { + "network-instances": { + "network-instance": [ + { + "name": "VRF1", + "interfaces": { + "interface": [ + { + "id": "Ethernet1/1", + "config": { + "id": "Ethernet1/1" + } + }, + { + "id": "Ethernet1/3", + "config": { + "id": "Ethernet1/3" + } + } + ] + }, + "protocols": { + "protocol": [ + { + "identifier": "STATIC", + "name": "static", + "static-routes": { + "static": [ + { + "prefix": "192.168.1.0/24", + "next-hops": { + "next-hop": [ + { + "index": "0", + "config": { + "index": "0", + "next-hop": "2001:db8:128:130::2" + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + } +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + #/network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform +* FFF diff --git a/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/metadata.textproto b/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/metadata.textproto new file mode 100644 index 00000000000..c93c5bf27d5 --- /dev/null +++ b/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "c356db98-353f-4e56-b242-902f5310a799" +plan_id: "RT-1.66" +description: "IPv4 Static Route with IPv6 Next-Hop" +testbed: TESTBED_DUT_ATE_4LINKS diff --git a/feature/system/aaa/feature.textproto b/feature/system/aaa/feature.textproto deleted file mode 100644 index 8dc185c3b3a..00000000000 --- a/feature/system/aaa/feature.textproto +++ /dev/null @@ -1,46 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_aaa" - version: 2 -} - -# Admin user authentication -config_path { - path: "/system/aaa/authentication/admin-user/config/admin-password" -} -telemetry_path { - path: "/system/aaa/authentication/admin-user/state/admin-password" -} -config_path { - path: "/system/aaa/authentication/admin-user/config/admin-password-hashed" -} -telemetry_path { - path: "/system/aaa/authentication/admin-user/state/admin-password-hashed" -} -telemetry_path { - path: "/system/aaa/authentication/admin-user/state/admin-username" -} - -#Local user authentication -config_path { - path: "/system/aaa/authentication/users/user/config/ssh-key" -} -config_path { - path: "/system/aaa/authentication/users/user/config/username" -} diff --git a/feature/system/aaa/tests/tacacs/README.md b/feature/system/aaa/tests/tacacs/README.md index d91e7603bb4..c7dd6af552e 100644 --- a/feature/system/aaa/tests/tacacs/README.md +++ b/feature/system/aaa/tests/tacacs/README.md @@ -121,6 +121,7 @@ This test suite aims to thoroughly validate the correct implementation of the A * Verification: * Use gNMI to get the device's configuration and validate that the accounting settings are correctly implemented as intended. + ## OpenConfig Path and RPC Coverage The below yaml defines the OC paths intended to be covered by this test. diff --git a/feature/system/aaa/tests/tacacs/metadata.textproto b/feature/system/aaa/tests/tacacs/metadata.textproto new file mode 100644 index 00000000000..08cab2be46f --- /dev/null +++ b/feature/system/aaa/tests/tacacs/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2e1c4f53-5d19-4fd0-a76e-987f0a2b97ed" +plan_id: "SYS-3.1" +description: "AAA and TACACS+ Configuration Verification Test Suite" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/system/attestz/feature.textproto b/feature/system/attestz/feature.textproto deleted file mode 100644 index 69517dc0029..00000000000 --- a/feature/system/attestz/feature.textproto +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_attestz" - version: 1 -} diff --git a/feature/system/bootz/feature.textproto b/feature/system/bootz/feature.textproto deleted file mode 100644 index e70994d5f07..00000000000 --- a/feature/system/bootz/feature.textproto +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2023 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_bootz" - version: 1 -} - -telemetry_path { - path: "/system/bootz/state/checksum" -} -telemetry_path { - path: "/system/bootz/state/error-count" -} -telemetry_path { - path: "/system/bootz/state/last-boot-attempt" -} -telemetry_path { - path: "/system/bootz/state/status" -} diff --git a/feature/system/control_plane_traffic/otg_tests/default_copp_test/README.md b/feature/system/control_plane_traffic/otg_tests/default_copp_test/README.md new file mode 100644 index 00000000000..d7c84a52f31 --- /dev/null +++ b/feature/system/control_plane_traffic/otg_tests/default_copp_test/README.md @@ -0,0 +1,175 @@ +# SYS-1.1: Test default COPP policy thresholds for Arista + +## Summary + +***NOTE:*** + +We will not configure any CoPP policies or thresholds on the DUT. We will solely rely on the vendor default and pre-applied CoPP policies and thresholds \ +[TODO] Utilize standardized OC models as they become available + +* Ensure that the default CoPP protects the control plane from excessive traffic and prioritizes critical control plane packets +* Verify that the default CoPP queues function correctly and handle packet drops as expected + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Topology: + +```mermaid +graph LR; +A[ATE] <-- Port1(IPv4/6) --> B[DUT]; +B[DUT] <-- Port2(IPv4/6) --> C[ATE]; +``` + +## Procedure + +### Initial setup + +* Connect DUT port-1, 2 to ATE port-1, 2 respectively +* Configure IPv4 and IPv6 addresses on DUT and ATE ports as shown below (using OC) + + * DUT port-1 IPv4 address ```dp1-v4 = 192.168.1.1/30``` + * ATE port-1 IPv4 address ```ap1-v4 = 192.168.1.2/30``` + + * DUT port-2 IPv4 address ```dp2-v4 = 192.168.1.5/30``` + * ATE port-2 IPv4 address ```ap2-v4 = 192.168.1.6/30``` + + * DUT port-1 IPv6 address ```dp1-v6 = 2001:DB8::1/126``` + * ATE port-1 IPv6 address ```ap1-v6 = 2001:DB8::2/126``` + + * DUT port-2 IPv6 address ```dp2-v6 = 2001:DB8::5/126``` + * ATE port-2 IPv6 address ```ap2-v6 = 2001:DB8::6/126``` + + +### SYS-1.1.1 - Test CoPP for BGP Traffic + +* Configure BGP between ATE Port-1 and DUT port-1 +* Generate BGP traffic (update/keepalive) from ATE port-1 within the system defined thresholds +* Generate BGP traffic (update/keepalive) from ATE port-1 exceeding the system defined thresholds +* Monitor CoPP telemetry for the BGP traffic class using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * BGP Session may be impacted when exceeding system defined thresholds + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.2 - Test L3 destination missing and Longest Prefix Match (LPM) overflow counters + +* Generate traffic from ATE port-1 for 1000 destinations with the IP addresses using `172.16.0.0/16` range +* The route for these destinations will/should not be present on DUT +* Generate traffic within the system defined thresholds and exceeding them +* Monitor CoPP telemetry for the L3 Destinatin Miss or LPM overflow traffic class using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.3 - Test System L2 Unicast thresholds + +* Generate traffic from ATE port-1 destined towards switch’s system mac address +* Generate traffic within and exceeding the system defined thresholds +* Monitor CoPP telemetry for System destined L2 Unicast traffic class using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.4 - Test System IP Unicast + +* Configure a loopback250 interface on the DUT with IP address `10.10.10.10/32` +* Generate IPv4 unicast traffic to the loopback250's IP Address +* Generate traffic within and exceeding the system defined thresholds +* Monitor CoPP telemetry for the system destined IP Unicast traffic class using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.5 - Test L2 Broadcast + +* Generate traffic from ATE port-1 to DUT and destined to `FF:FF:FF:FF:FF:FF` broadcast mac address +* Generate traffic within and exceeding the system defined thresholds +* Monitor CoPP telemetry for the L2 Broadcast traffic class using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.6 - Test LACP thresholds + +* Generate random LACP Data Units (LACPDUs) from ATE port-1 to DUT +* Generate traffic within and exceeding the system defined thresholds +* Monitor CoPP telemetry for LACP traffic using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.7 - Test LDP Thresholds + +* Configure LDP session between ATE port-1 and DUT port-1 +* Generate LDP traffic within and exceeding the system defined thresholds +* Monitor CoPP telemetry for LDP traffic using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * LDP session remains up throughout the test + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +### SYS-1.1.8 - Test System LLDP + +* Enable LLDP on DUT port-1 +* Generate LLDP packets from ATE port-1 to DUT port-1 within and exceeding the system defined thresholds +* Monitor CoPP telemetry for LLDP traffic using the best available method to verify that: + * ***[TODO]*** **gNMI/OC:** Query standardized OC CoPP state model and use OC path via gNMI + * `/components/component/integrated-circuit/pipeline-counters/control-plane-traffic/vendor/..` + * Packets exceeding the threshold are dropped for each queue + * Packets within the threshold are not dropped + * Log the CPU utilization to ensure it remains < 100% under traffic stress + * `/system/cpus/cpu/state/total/instant` + +## Canonical OC + +***NOTE:*** When the goal evolves to testing configurable CoPP, this section would ideally contain the (future) standardized OC CoPP config model + +```json +{} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Interface IP Address + /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv4/addresses/address/config/prefix-length: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/ip: + /interfaces/interface/subinterfaces/subinterface/ipv6/addresses/address/config/prefix-length: + # LLDP: + /lldp/interfaces/interface/config/name: + /lldp/interfaces/interface/config/enabled: + # LDP: + /network-instances/network-instance/mpls/signaling-protocols/ldp/global/config/lsr-id: + /network-instances/network-instance/mpls/signaling-protocols/ldp/interface-attributes/interfaces/interface/config/interface-id: + +rpcs: + gnmi: + gNMI.Set: +``` + +## Minimum DUT platform requirement + +* FFF \ No newline at end of file diff --git a/feature/system/control_plane_traffic/otg_tests/default_copp_test/default_copp_test.go b/feature/system/control_plane_traffic/otg_tests/default_copp_test/default_copp_test.go new file mode 100644 index 00000000000..ac1e35e43b8 --- /dev/null +++ b/feature/system/control_plane_traffic/otg_tests/default_copp_test/default_copp_test.go @@ -0,0 +1,484 @@ +// Copyright 2025 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 default_copp_test + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "strings" + "testing" + "time" + + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + broadcastMAC = "FF:FF:FF:FF:FF:FF" + unknownMAC = "02:10:02:01:01:01" + slowMAC = "01:80:c2:00:00:02" + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + ipv4DstPfx = "172.16.0.0" + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag + thresholdCPUUtilization = 100 + etherTypeLACP = 0x8809 + dutIncomingPort = "port1" +) + +var ( + dutSrc = attrs.Attributes{ + Desc: "dutSrc", + IPv4: "192.168.1.1", + IPv6: "2001:DB8::1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + ateSrc = attrs.Attributes{ + Name: "ateSrc", + IPv4: "192.168.1.2", + MAC: "02:00:01:01:01:01", + IPv6: "2001:DB8::2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutDst = attrs.Attributes{ + Desc: "dutDst", + IPv4: "192.168.1.5", + IPv6: "2001:DB8::5", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + ateDst = attrs.Attributes{ + Name: "ateDst", + IPv4: "192.168.1.6", + MAC: "02:00:02:01:01:01", + IPv6: "2001:DB8::6", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } +) + +type flowParameters struct { + pps uint64 + packetSize uint32 + trafficLayer uint8 + trafficType string + dstMACAddress string + srcIPAddress string + dstIPAddress string +} + +type commonEntities struct { + dut *ondatra.DUTDevice + ate *ondatra.ATEDevice + gnmiClient gpb.GNMIClient + ctx context.Context +} + +type coppSystemTestcase struct { + name string + flowParams flowParameters + increasedDropCount bool + counters []string +} + +// configInterfaceDUT configures the interface with the Addrs. +func (ce *commonEntities) configInterfaceDUT(t *testing.T, i *oc.Interface, a *attrs.Attributes) *oc.Interface { + t.Helper() + + i.SetDescription(a.Desc) + i.SetType(oc.IETFInterfaces_InterfaceType_ethernetCsmacd) + + if deviations.InterfaceEnabled(ce.dut) { + i.SetEnabled(true) + } + s := i.GetOrCreateSubinterface(0) + + s4 := s.GetOrCreateIpv4() + if deviations.InterfaceEnabled(ce.dut) && !deviations.IPv4MissingEnabled(ce.dut) { + s4.SetEnabled(true) + } + s4a := s4.GetOrCreateAddress(a.IPv4) + s4a.SetPrefixLength(uint8(ipv4PrefixLen)) + + s6 := s.GetOrCreateIpv6() + if deviations.InterfaceEnabled(ce.dut) { + s6.SetEnabled(true) + } + s6.GetOrCreateAddress(a.IPv6).SetPrefixLength(uint8(ipv6PrefixLen)) + + return i +} + +// configureDUT configures port1, port2 on the DUT. +func (ce *commonEntities) configureDUT(t *testing.T) { + t.Helper() + + d := gnmi.OC() + + p1 := ce.dut.Port(t, "port1") + i1 := &oc.Interface{Name: ygot.String(p1.Name())} + i1.SetEnabled(true) + gnmi.Update(t, ce.dut, d.Interface(p1.Name()).Config(), ce.configInterfaceDUT(t, i1, &dutSrc)) + + p2 := ce.dut.Port(t, "port2") + i2 := &oc.Interface{Name: ygot.String(p2.Name())} + i2.SetEnabled(true) + gnmi.Update(t, ce.dut, d.Interface(p2.Name()).Config(), ce.configInterfaceDUT(t, i2, &dutDst)) +} + +// configureOTG configures port1 and port2 on the ATE. +func configureOTG(t *testing.T) gosnappi.Config { + t.Helper() + + top := gosnappi.NewConfig() + + // Port1 Configuration. + port1 := top.Ports().Add().SetName("port1") + iDut1Dev := top.Devices().Add().SetName(ateSrc.Name) + iDut1Eth := iDut1Dev.Ethernets().Add().SetName(ateSrc.Name + ".Eth").SetMac(ateSrc.MAC) + iDut1Eth.Connection().SetPortName(port1.Name()) + iDut1Ipv4 := iDut1Eth.Ipv4Addresses().Add().SetName(ateSrc.Name + ".IPv4") + iDut1Ipv4.SetAddress(ateSrc.IPv4).SetGateway(dutSrc.IPv4).SetPrefix(uint32(ateSrc.IPv4Len)) + iDut1Ipv6 := iDut1Eth.Ipv6Addresses().Add().SetName(ateSrc.Name + ".IPv6") + iDut1Ipv6.SetAddress(ateSrc.IPv6).SetGateway(dutSrc.IPv6).SetPrefix(uint32(ateSrc.IPv6Len)) + + // Port2 Configuration. + port2 := top.Ports().Add().SetName("port2") + iDut2Dev := top.Devices().Add().SetName(ateDst.Name) + iDut2Eth := iDut2Dev.Ethernets().Add().SetName(ateDst.Name + ".Eth").SetMac(ateDst.MAC) + iDut2Eth.Connection().SetPortName(port2.Name()) + iDut2Ipv4 := iDut2Eth.Ipv4Addresses().Add().SetName(ateDst.Name + ".IPv4") + iDut2Ipv4.SetAddress(ateDst.IPv4).SetGateway(dutDst.IPv4).SetPrefix(uint32(ateDst.IPv4Len)) + iDut2Ipv6 := iDut2Eth.Ipv6Addresses().Add().SetName(ateDst.Name + ".IPv6") + iDut2Ipv6.SetAddress(ateDst.IPv6).SetGateway(dutDst.IPv6).SetPrefix(uint32(ateDst.IPv6Len)) + + return top +} + +// getDroppedPktsForCounter returns the dropped packet count for a given counter. +func getDroppedPktsForCounter(t *testing.T, jsonData []byte, counterName string) float64 { + t.Helper() + + logAndReturnErroredCount := func(format string, args ...any) float64 { + t.Errorf(format, args...) + return -1 + } + var data map[string]any + if err := json.Unmarshal(jsonData, &data); err != nil { + return logAndReturnErroredCount("getDroppedPktsForCounter: Error unmarshalling JSON: %v", err) + } + counterMap, ok := data[counterName].(map[string]any) + if !ok { + return logAndReturnErroredCount("getDroppedPktsForCounter: Error getting stats for counter: %s", counterName) + } + dropped, ok := counterMap["dropped"] + if !ok { + return logAndReturnErroredCount("getDroppedPktsForCounter: Error getting stats for counter: %s", counterName) + } + droppedStr, ok := dropped.(string) + if !ok { + return logAndReturnErroredCount("getDroppedPktsForCounter: Error converting dropped value to string for counter: %s", counterName) + } + packetsDropped, err := strconv.ParseFloat(droppedStr, 64) + if err != nil { + return logAndReturnErroredCount("getDroppedPktsForCounter: Error getting packets dropped for counter: %s", counterName) + } + return packetsDropped +} + +// createTrafficFlows creates traffic flows for the given flow parameters. +func (ce *commonEntities) createTrafficFlows(t *testing.T, top gosnappi.Config, flowParams *flowParameters) { + t.Helper() + + flowName := fmt.Sprintf("%d-%s-Flow:", flowParams.trafficLayer, flowParams.trafficType) + + flow := top.Flows().Add().SetName(flowName) + flow.TxRx().Port(). + SetTxName(ce.ate.Port(t, "port1").ID()). + SetRxNames([]string{ce.ate.Port(t, "port2").ID()}) + + flow.Metrics().SetEnable(true) + flow.Rate().SetPps(flowParams.pps) + flow.Size().SetFixed(flowParams.packetSize) + flow.Duration().Continuous() + + eth := flow.Packet().Add().Ethernet() + if flowParams.trafficType == "l3LpmOverflow" { + eth.Src().SetValue(unknownMAC) + } else { + eth.Src().SetValue(ateSrc.MAC) + } + + if flowParams.trafficType == "l2Bcast" { + eth.Dst().SetValue(flowParams.dstMACAddress) + } else if flowParams.trafficType == "lacp" { + eth.Dst().SetValue(slowMAC) + eth.EtherType().SetValue(etherTypeLACP) + } else { + dutDstInterface := ce.dut.Port(t, "port1").Name() + dstMac := gnmi.Get(t, ce.dut, gnmi.OC().Interface(dutDstInterface).Ethernet().MacAddress().State()) + eth.Dst().SetValue(dstMac) + } + + if flowParams.trafficLayer == 3 { + ip := flow.Packet().Add().Ipv4() + if flowParams.srcIPAddress != "" { + ip.Src().SetValue(flowParams.srcIPAddress) + } else { + ip.Src().SetValue(ateSrc.IPv4) + } + + if flowParams.dstIPAddress != "" { + ip.Dst().SetValue(flowParams.dstIPAddress) + } else { + dstIP := ipv4DstPfx + ip.Dst().Increment().SetStart(dstIP).SetCount(200) + } + } +} + +// checkCPUUtilization checks the CPU utilization of the device. +func (ce *commonEntities) checkCPUUtilization(t *testing.T) error { + t.Helper() + + dut := ondatra.DUT(t, "dut") + t.Helper() + cpuList := gnmi.OC().System().CpuAny().State() + cpus := gnmi.GetAll(t, dut, cpuList) + for _, cpu := range cpus { + cpuUtil := gnmi.OC().System().Cpu(cpu.GetIndex()).Total().Instant().State() + utilization := gnmi.Get(t, dut, cpuUtil) + if utilization >= thresholdCPUUtilization { + return fmt.Errorf("checkCPUUtilization: high CPU utilization seen, cpu name: %d, output: %d%%", cpu.GetIndex(), utilization) + } + t.Logf("checkCPUUtilization: CPU utilization within limit, cpu name: %d, output: %d%%\n", cpu.GetIndex(), utilization) + } + return nil +} + +// runTraffic starts and stops the traffic flow. +func (ce *commonEntities) runTraffic(t *testing.T) { + t.Helper() + + t.Log("Starting traffic for 15 seconds") + ce.ate.OTG().StartTraffic(t) + for idx := 0; idx < 3; idx++ { + time.Sleep(5 * time.Second) + if err := ce.checkCPUUtilization(t); err != nil { + t.Errorf("runTraffic: CPU utilization check failed: %v", err) + } + } + + t.Log("Stopping traffic and waiting 10 seconds for traffic stats to complete") + ce.ate.OTG().StopTraffic(t) + time.Sleep(10 * time.Second) +} + +func removeAfterLastSlash(str string) string { + lastSlashIndex := strings.LastIndex(str, "/") + if lastSlashIndex == -1 { + return str + } + return str[:lastSlashIndex] +} + +// getDroppedPktCounts returns the dropped packet counts for the given counters. +func (ce *commonEntities) getDroppedPktCounts(t *testing.T, counters []string) []float64 { + t.Helper() + p1 := ce.dut.Port(t, dutIncomingPort) + pName := removeAfterLastSlash(p1.Name()) + ethernetPort := gnmi.Get(t, ce.dut, gnmi.OC().Component(pName).Parent().State()) + switchName := gnmi.Get(t, ce.dut, gnmi.OC().Component(ethernetPort).Parent().State()) + + getResponse, err := ce.gnmiClient.Get(context.Background(), &gpb.GetRequest{ + Path: []*gpb.Path{{ + Elem: []*gpb.PathElem{ + {Name: "components"}, + {Name: "component", Key: map[string]string{"name": switchName}}, + {Name: "integrated-circuit"}, + {Name: "pipeline-counters"}, + {Name: "control-plane-traffic"}, + {Name: "vendor"}, + {Name: "arista"}, + {Name: "sand"}, + {Name: "state"}, + }, + }}, + Type: gpb.GetRequest_STATE, + Encoding: gpb.Encoding_JSON_IETF, + }) + + var droppedPkts []float64 + if err != nil { + t.Errorf("getDroppedPktCounts: error during gNMI Get: %s", err) + return droppedPkts + } + notifications := getResponse.GetNotification() + jsonData := notifications[0].GetUpdate()[0].GetVal().GetJsonIetfVal() + for _, counter := range counters { + droppedPkts = append(droppedPkts, getDroppedPktsForCounter(t, jsonData, counter)) + } + return droppedPkts +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// testCoppSystemHelper is a helper function for testing COPP system. +func (ce *commonEntities) testCoppSystemHelper(t *testing.T, tc *coppSystemTestcase) { + t.Helper() + + initialPktCounts := ce.getDroppedPktCounts(t, tc.counters) + t.Logf("Configure OTG") + top := configureOTG(t) + ce.createTrafficFlows(t, top, &tc.flowParams) + + t.Log("Pushing the following config to the OTG device") + t.Log(top.String()) + otgObj := ce.ate.OTG() + otgObj.PushConfig(t, top) + otgObj.StartProtocols(t) + + t.Logf("Get initial interface counters for port: %v", dutIncomingPort) + initialCounters := gnmi.Get(t, ce.dut, gnmi.OC().Interface(ce.dut.Port(t, dutIncomingPort).Name()).Counters().State()) + initialInPkts := initialCounters.GetInPkts() + t.Logf("Initial incoming packets: %v", initialInPkts) + ce.runTraffic(t) + otgObj.StopProtocols(t) + t.Logf("Get final interface counters for port: %v", dutIncomingPort) + finalCounters := gnmi.Get(t, ce.dut, gnmi.OC().Interface(ce.dut.Port(t, dutIncomingPort).Name()).Counters().State()) + finalInPkts := finalCounters.GetInPkts() + t.Logf("Final incoming packets: %v", finalInPkts) + finalPktCounts := ce.getDroppedPktCounts(t, tc.counters) + for idx, counter := range tc.counters { + if tc.increasedDropCount && finalPktCounts[idx] <= initialPktCounts[idx] { + t.Errorf("testCoppSystemHelper: Drop count validation failed for testcase: %s, counter: %s. Final dropped pkt count: %v, Initial dropped pkt count: %v", tc.name, counter, finalPktCounts[idx], initialPktCounts[idx]) + continue + } + if tc.increasedDropCount == false && finalPktCounts[idx] != initialPktCounts[idx] { + t.Errorf("testCoppSystemHelper: Drop count validation failed for testcase: %s, counter: %s. Final dropped pkt count: %v, Initial dropped pkt count: %v", tc.name, counter, finalPktCounts[idx], initialPktCounts[idx]) + continue + } + t.Logf("testCoppSystemHelper: Drop count validation success for testcase: %s, counter: %s. Final dropped pkt count: %v, Initial dropped pkt count: %v", tc.name, counter, finalPktCounts[idx], initialPktCounts[idx]) + } +} + +// TestCoppSystem tests the COPP system. It configures the DUT and ATE, +// and then runs a series of tests to validate the COPP system. +func TestCoppSystem(t *testing.T) { + ctx := context.Background() + + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + + gnmiClient, err := dut.RawAPIs().BindingDUT().DialGNMI(ctx) + if err != nil { + t.Errorf("TestCoppSystem: failed to dial gNMI: %v", err) + return + } + + ce := &commonEntities{ + dut: dut, + ate: ate, + gnmiClient: gnmiClient, + ctx: ctx, + } + + ce.configureDUT(t) + // TODO [https://github.com/openconfig/featureprofiles/issues/4171]: Add test cases for BGP, LDP and LLDP traffic. + // Add test case for arista-sand-control-plane-traffic-counters:l3-destination-miss. + testCases := []coppSystemTestcase{ + { + name: "CoppSystemL3LpmOverflowExceedingLimitTest", + flowParams: flowParameters{pps: 20000, packetSize: 512, trafficLayer: 3, trafficType: "l3LpmOverflow"}, + increasedDropCount: true, + counters: []string{"arista-sand-control-plane-traffic-counters:l3-lpm-overflow"}, + }, + { + name: "CoppSystemL3LpmOverflowInLimitTest", + flowParams: flowParameters{pps: 200, packetSize: 512, trafficLayer: 3, trafficType: "l3LpmOverflow"}, + increasedDropCount: false, + counters: []string{"arista-sand-control-plane-traffic-counters:l3-lpm-overflow"}, + }, + { + name: "CoppSystemL2UcastExceedingLimitTest", + flowParams: flowParameters{pps: 600000, packetSize: 512, trafficLayer: 2}, + increasedDropCount: true, + counters: []string{"arista-sand-control-plane-traffic-counters:l2-unicast"}, + }, + { + name: "CoppSystemL2UcastInLimitTest", + flowParams: flowParameters{pps: 600, packetSize: 512, trafficLayer: 2}, + increasedDropCount: false, + counters: []string{"arista-sand-control-plane-traffic-counters:l2-unicast"}, + }, + { + name: "CoppSystemIpUcastExceedingLimitTest", + flowParams: flowParameters{pps: 600000, packetSize: 512, trafficLayer: 3, trafficType: "ipUcast", dstIPAddress: dutSrc.IPv4}, + increasedDropCount: true, + counters: []string{"arista-sand-control-plane-traffic-counters:ip-unicast"}, + }, + { + name: "CoppSystemIpUcastInLimitTest", + flowParams: flowParameters{pps: 600, packetSize: 512, trafficLayer: 3, trafficType: "ipUcast", dstIPAddress: dutSrc.IPv4}, + increasedDropCount: false, + counters: []string{"arista-sand-control-plane-traffic-counters:ip-unicast"}, + }, + { + name: "CoppSystemL2BcastExceedingLimitTest", + flowParams: flowParameters{pps: 600000, packetSize: 512, trafficLayer: 2, trafficType: "l2Bcast", dstMACAddress: broadcastMAC}, + increasedDropCount: true, + counters: []string{"arista-sand-control-plane-traffic-counters:l2-broadcast"}, + }, + { + name: "CoppSystemL2BcastInLimitTest", + flowParams: flowParameters{pps: 600, packetSize: 512, trafficLayer: 2, trafficType: "l2Bcast", dstMACAddress: broadcastMAC}, + increasedDropCount: false, + counters: []string{"arista-sand-control-plane-traffic-counters:l2-broadcast"}, + }, + { + name: "CoppSystemLacpExceedingLimitTest", + flowParams: flowParameters{pps: 600000, packetSize: 512, trafficLayer: 2, trafficType: "lacp"}, + increasedDropCount: true, + counters: []string{"arista-sand-control-plane-traffic-counters:lacp"}, + }, + { + name: "CoppSystemLacpInLimitTest", + flowParams: flowParameters{pps: 600, packetSize: 512, trafficLayer: 2, trafficType: "lacp"}, + increasedDropCount: false, + counters: []string{"arista-sand-control-plane-traffic-counters:lacp"}, + }, + } + + for idx := range testCases { + tc := &testCases[idx] + t.Run(tc.name, func(t *testing.T) { + ce.testCoppSystemHelper(t, tc) + }) + } +} diff --git a/feature/system/control_plane_traffic/otg_tests/default_copp_test/metadata.textproto b/feature/system/control_plane_traffic/otg_tests/default_copp_test/metadata.textproto new file mode 100644 index 00000000000..d23baa22fbb --- /dev/null +++ b/feature/system/control_plane_traffic/otg_tests/default_copp_test/metadata.textproto @@ -0,0 +1,30 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata +uuid: "f9cbcdfa-5c51-4eea-a505-cb3145793738" +plan_id: "SYS-1.1" +description: "Test default COPP policy thresholds for Arista" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: NOKIA + } + deviations: { + interface_enabled: true + } +} +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + } +} \ No newline at end of file diff --git a/feature/system/feature.textproto b/feature/system/feature.textproto deleted file mode 100644 index 177499d7c8e..00000000000 --- a/feature/system/feature.textproto +++ /dev/null @@ -1,121 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system" - version: 2 -} - -# Name -config_path { - path: "/system/config/hostname" -} -telemetry_path { - path: "/system/state/hostname" -} -config_path { - path: "/system/config/domain-name" -} -telemetry_path { - path: "/system/state/domain-name" -} - -# Banner -config_path { - path: "/system/config/login-banner" -} -telemetry_path { - path: "/system/state/login-banner" -} -config_path { - path: "/system/config/motd-banner" -} -telemetry_path { - path: "/system/state/motd-banner" -} - -# Time -telemetry_path { - path: "/system/state/current-datetime" -} - -# boot parameters -telemetry_path { - path: "/system/state/boot-time" -} - -# CPU -telemetry_path { - path: "/system/cpus/cpu/state/total/avg" -} -telemetry_path { - path: "/system/cpus/cpu/state/total/interval" -} -telemetry_path { - path: "/system/cpus/cpu/state/index" -} -telemetry_path { - path: "/system/cpus/cpu/state/kernel/avg" -} -telemetry_path { - path: "/system/cpus/cpu/state/nice/avg" -} -telemetry_path { - path: "/system/cpus/cpu/state/software-interrupt/avg" -} -telemetry_path { - path: "/system/cpus/cpu/state/user/avg" -} -telemetry_path { - path: "/system/cpus/cpu/state/wait/avg" -} -telemetry_path { - path: "/system/cpus/cpu/state/wait/interval" -} - -# Memory -telemetry_path { - path: "/system/memory/state/reserved" -} - -# Timezone -config_path { - path: "/system/clock/config/timezone-name" -} -telemetry_path { - path: "/system/clock/state/timezone-name" -} - -# Alarms -telemetry_path { - path: "/system/alarms/alarm/state/id" -} -telemetry_path { - path: "/system/alarms/alarm/state/resource" -} -telemetry_path { - path: "/system/alarms/alarm/state/severity" -} -telemetry_path { - path: "/system/alarms/alarm/state/text" -} -telemetry_path { - path: "/system/alarms/alarm/state/time-created" -} -telemetry_path { - path: "/system/alarms/alarm/state/type-id" -} diff --git a/feature/system/gnmi/cliorigin/feature.textproto b/feature/system/gnmi/cliorigin/feature.textproto deleted file mode 100644 index 1d84d99726b..00000000000 --- a/feature/system/gnmi/cliorigin/feature.textproto +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_gnmi_cliorigin" - version: 1 -} - -gnmi_service { - method_name: MD_GET -} - -gnmi_service { - method_name: MD_SET -} diff --git a/feature/system/gnmi/feature.textproto b/feature/system/gnmi/feature.textproto deleted file mode 100644 index b6bc47c2e4b..00000000000 --- a/feature/system/gnmi/feature.textproto +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_gnmi" - version: 1 -} - -config_path { - path: "/system/grpc-servers/grpc-server/config/name" -} -telemetry_path { - path: "/system/grpc-servers/grpc-server/state/name" -} -config_path { - # value GNMI only. - path: "/system/grpc-servers/grpc-server/config/services" -} -telemetry_path { - path: "/system/grpc-servers/grpc-server/state/services" -} -config_path { - path: "/system/grpc-servers/grpc-server/config/enable" -} -telemetry_path { - path: "/system/grpc-servers/grpc-server/state/enable" -} -config_path { - path: "/system/grpc-servers/grpc-server/config/port" -} -telemetry_path { - path: "/system/grpc-servers/grpc-server/state/port" -} diff --git a/feature/system/gnmi/get/feature.textproto b/feature/system/gnmi/get/feature.textproto deleted file mode 100644 index 55fd8121dd9..00000000000 --- a/feature/system/gnmi/get/feature.textproto +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_gnmi_get" - version: 1 -} - -gnmi_service { - method_name: MD_GET -} diff --git a/feature/system/gnmi/get/tests/system_gnmi_get_test/README.md b/feature/system/gnmi/get/tests/system_gnmi_get_test/README.md deleted file mode 100644 index 2f915aa9dff..00000000000 --- a/feature/system/gnmi/get/tests/system_gnmi_get_test/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# gNMI-1.9: Get requests - -TODO(robshakir): fill in test plan from code already written. diff --git a/feature/system/gnmi/metadata/feature.textproto b/feature/system/gnmi/metadata/feature.textproto deleted file mode 100644 index e85b54ab1db..00000000000 --- a/feature/system/gnmi/metadata/feature.textproto +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_gnmi_metadata" - version: 1 -} - -gnmi_service { - method_name: MD_GET -} - -gnmi_service { - method_name: MD_SET -} diff --git a/feature/system/gnmi/set/feature.textproto b/feature/system/gnmi/set/feature.textproto deleted file mode 100644 index 30b70f2e858..00000000000 --- a/feature/system/gnmi/set/feature.textproto +++ /dev/null @@ -1,25 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_gnmi_set" - version: 1 -} - -gnmi_service { - method_name: MD_SET -} diff --git a/feature/system/ntp/feature.textproto b/feature/system/ntp/feature.textproto deleted file mode 100644 index ad945baa9dd..00000000000 --- a/feature/system/ntp/feature.textproto +++ /dev/null @@ -1,61 +0,0 @@ -# Copyright 2022 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 -# -# https://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. -# -# proto-file: github.com/openconfig/featureprofiles/proto/feature.proto -# proto-message: FeatureProfile - -id { - name: "system_ntp" - version: 1 -} - -config_path { - path: "/system/ntp/config/enabled" -} -telemetry_path { - path: "/system/ntp/state/enabled" -} -config_path { - path: "/system/ntp/servers/server/config/address" -} -telemetry_path { - path: "/system/ntp/servers/server/state/address" -} -telemetry_path { - path: "/system/ntp/servers/server/state/port" -} -config_path { - path: "/system/ntp/servers/server/config/source-address" -} -telemetry_path { - path: "/system/ntp/servers/server/state/source-address" -} - - -# Counters -telemetry_path { - path: "/system/ntp/servers/server/state/offset" -} -telemetry_path { - path: "/system/ntp/servers/server/state/poll-interval" -} -telemetry_path { - path: "/system/ntp/servers/server/state/root-delay" -} -telemetry_path { - path: "/system/ntp/servers/server/state/root-dispersion" -} -telemetry_path { - path: "/system/ntp/servers/server/state/stratum" -} diff --git a/feature/system/ntp/tests/system_ntp_test/metadata.textproto b/feature/system/ntp/tests/system_ntp_test/metadata.textproto index 28b530604bb..727c68c5410 100644 --- a/feature/system/ntp/tests/system_ntp_test/metadata.textproto +++ b/feature/system/ntp/tests/system_ntp_test/metadata.textproto @@ -13,3 +13,11 @@ platform_exceptions: { ntp_non_default_vrf_unsupported: true } } +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ntp_source_address_unsupported: true + } +} \ No newline at end of file diff --git a/feature/system/ntp/tests/system_ntp_test/system_ntp_test.go b/feature/system/ntp/tests/system_ntp_test/system_ntp_test.go index fa8820f2f82..46bb3f184e7 100644 --- a/feature/system/ntp/tests/system_ntp_test/system_ntp_test.go +++ b/feature/system/ntp/tests/system_ntp_test/system_ntp_test.go @@ -21,6 +21,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/helpers" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -92,37 +93,66 @@ func TestNtpServerConfigurability(t *testing.T) { } for _, testCase := range testCases { - t.Run(testCase.description, func(t *testing.T) { - if testCase.vrf != "" && deviations.NtpNonDefaultVrfUnsupported(dut) { - t.Skip("NTP non default vrf unsupported") - } - ntpPath := gnmi.OC().System().Ntp() + // NTP source address is not supported, CLI is used to configure NTP source interface. + if deviations.NtpSourceAddressUnsupported(dut) { + t.Run(testCase.description, func(t *testing.T) { + if testCase.vrf != "" && deviations.NtpNonDefaultVrfUnsupported(dut) { + t.Skip("NTP non default vrf unsupported") + } + for _, address := range testCase.addresses { + if testCase.vrf != "" { + ntpServer := fmt.Sprintf("ntp server vrf %s %s version 4 source %s ", testCase.vrf, address, loopbackIntfName) + helpers.GnmiCLIConfig(t, dut, ntpServer) + } else { + ntpServer := fmt.Sprintf("ntp server %s version 4 source %s ", address, loopbackIntfName) + helpers.GnmiCLIConfig(t, dut, ntpServer) + } + } + ntpPath := gnmi.OC().System().Ntp() + ntpState := gnmi.Get(t, dut, ntpPath.State()) + for _, address := range testCase.addresses { + ntpServer := ntpState.GetServer(address) + if ntpServer == nil { + t.Errorf("Missing NTP server from NTP state: %s", address) + } + if got, want := ntpServer.GetNetworkInstance(), testCase.vrf; want != "" && got != want { + t.Errorf("Incorrect NTP Server network instance for address %s: got %s, want %s", address, got, want) + } + } + }) + } else { + t.Run(testCase.description, func(t *testing.T) { + if testCase.vrf != "" && deviations.NtpNonDefaultVrfUnsupported(dut) { + t.Skip("NTP non default vrf unsupported") + } + ntpPath := gnmi.OC().System().Ntp() - d := &oc.Root{} + d := &oc.Root{} - ntp := d.GetOrCreateSystem().GetOrCreateNtp() - ntp.SetEnabled(true) - for _, address := range testCase.addresses { - server := ntp.GetOrCreateServer(address) - server.SetSourceAddress(dutlo0Attrs.IPv4) - if testCase.vrf != "" { - server.SetNetworkInstance(testCase.vrf) + ntp := d.GetOrCreateSystem().GetOrCreateNtp() + ntp.SetEnabled(true) + for _, address := range testCase.addresses { + server := ntp.GetOrCreateServer(address) + server.SetSourceAddress(dutlo0Attrs.IPv4) + if testCase.vrf != "" { + server.SetNetworkInstance(testCase.vrf) + } } - } - gnmi.Replace(t, dut, ntpPath.Config(), ntp) + gnmi.Replace(t, dut, ntpPath.Config(), ntp) - ntpState := gnmi.Get(t, dut, ntpPath.State()) - for _, address := range testCase.addresses { - ntpServer := ntpState.GetServer(address) - if ntpServer == nil { - t.Errorf("Missing NTP server from NTP state: %s", address) - } - if got, want := ntpServer.GetNetworkInstance(), testCase.vrf; want != "" && got != want { - t.Errorf("Incorrect NTP Server network instance for address %s: got %s, want %s", address, got, want) + ntpState := gnmi.Get(t, dut, ntpPath.State()) + for _, address := range testCase.addresses { + ntpServer := ntpState.GetServer(address) + if ntpServer == nil { + t.Errorf("Missing NTP server from NTP state: %s", address) + } + if got, want := ntpServer.GetNetworkInstance(), testCase.vrf; want != "" && got != want { + t.Errorf("Incorrect NTP Server network instance for address %s: got %s, want %s", address, got, want) + } } - } - }) + }) + } } } diff --git a/feature/system/bootz/tests/README.md b/feature/system/secure_boot/tests/bootz/README.md similarity index 100% rename from feature/system/bootz/tests/README.md rename to feature/system/secure_boot/tests/bootz/README.md diff --git a/feature/system/secure_boot/tests/bootz/metadata.textproto b/feature/system/secure_boot/tests/bootz/metadata.textproto new file mode 100644 index 00000000000..dca3a505e37 --- /dev/null +++ b/feature/system/secure_boot/tests/bootz/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "9842b7dd-6877-440b-bf8f-8272a1e31d5f" +plan_id: "bootz" +description: "General bootz bootstrap tests" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/system/system_base_test/tests/system_software_version/README.md b/feature/system/system_base_test/tests/system_software_version/README.md new file mode 100644 index 00000000000..d62208a8c8b --- /dev/null +++ b/feature/system/system_base_test/tests/system_software_version/README.md @@ -0,0 +1,38 @@ +# System-1.5: System software-version test + +## Summary + +Ensures the device support basic system requirements for a device supporting g* APIs. + +### Procedure + +Each test will require the DUT configured with a basic service configuration that +should be provided as part of the basic configuration. This setup should also include +any security setup for connecting to the services. + +### Tests + +1. Configure DUT with service configurations for all required services +2. The test will verify if the software-version state path can be read and is non-empty. + +#### Canonical OC +```json +{ + "system": { + "state": { + "software-version": "1.4.2" + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + /system/state/software-version: + +rpcs: + gnmi: + gNMI.Subscribe: +``` diff --git a/feature/system/system_base_test/tests/system_software_version/metadata.textproto b/feature/system/system_base_test/tests/system_software_version/metadata.textproto new file mode 100644 index 00000000000..4b521c8f567 --- /dev/null +++ b/feature/system/system_base_test/tests/system_software_version/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "8bfbf7b1-ab92-47bd-a64b-fe84f5067927" +plan_id: "System-1.5" +description: "System software-version test" +testbed: TESTBED_DUT diff --git a/feature/system/system_base_test/tests/system_software_version/system_software_version_test.go b/feature/system/system_base_test/tests/system_software_version/system_software_version_test.go new file mode 100644 index 00000000000..fcac11bf34c --- /dev/null +++ b/feature/system/system_base_test/tests/system_software_version/system_software_version_test.go @@ -0,0 +1,35 @@ +/* + Copyright 2025 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 + https://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_software_version_test + +import ( + "testing" + + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" +) + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +// TestSoftwareVersion verifies that the software version state path can be read and is not empty. +// telemetry_path:/system/state/software-version +func TestSoftwareVersion(t *testing.T) { + dut := ondatra.DUT(t, "dut") + if got := gnmi.Get(t, dut, gnmi.OC().System().SoftwareVersion().State()); got == "" { + t.Error("Telemetry software version is empty, want non-empty") + } +} diff --git a/feature/ttl/otg_tests/egress/README.md b/feature/ttl/otg_tests/egress/README.md new file mode 100644 index 00000000000..9da32320876 --- /dev/null +++ b/feature/ttl/otg_tests/egress/README.md @@ -0,0 +1,433 @@ +# PF-1.9: Egress handling of TTL + +## Summary + +This test verifies TTL handling for egress flows. + +## Testbed Type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + +* DUT has one ingress port and one egress port, both connected to ATE. + + ``` + ------- + | | + [ ATE:Port1 ] ---- | DUT | ---- [ ATE:Port2 ] + | | + ------- + ``` + +* Routes are advertised from ATE:Port2. +* Traffic is generated from ATE:Port1. +* ATE:Port2 is used as the destination port for flows. +* IPv4-DST-DECAP is 10.2.2.2 +* Frame size for packets generated from ATE:Port1 is 128 bytes + +#### Configuration + +1. DUT:Port1 is configured as Singleton IP interface towards ATE:Port1. + +2. DUT:Port2 is configured as Singleton IP interface towards ATE:Port2. + +3. DUT is configured witht he following interface IPs + * DUT:Port1:IPv4 is 192.168.10.2/30 + * DUT:Port1:IPv6 is 2001:DB8::192:168:10:2/126 + * DUT:Port2:IPv4 is 192.168.20.2/30 + * DUT:POrt2:IPv6 is 2001:DB8::192:168:20:2/126 + +4. ATE is configured witht he following interface IPs + * ATE:Port1:IPv4 is 192.168.10.1/30 + * ATE:Port1:IPv6 is 2001:DB8::192:168:10:1/126 + * ATE:Port2:IPv4 is 192.168.20.1/30 + * ATE:Port2:IPv6 is 2001:DB8::192:168:20:1/126 + +5. DUT is configured to decapsulate packets destined to IPv4-DST-DECAP/32 + +6. DUT is configured with static LSP with label 100010 pointing to + ATE:Port2:IPv4 address. This should be used for encapsulated packets with + inner IPv4. + +7. DUT is configured with static LSP with label 100020 pointing to + ATE:Port2:IPv6 address. This should be used for encapsulated packets with + inner IPv6. + + +### PF-1.9.1: IPv4 non-encapsulated traffic with TTL = 10. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to ATE:Port2:IPv4. + * Set TTL of all packets to *10*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* DUT interface DUT:Port2 `out-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* The packet count of traffic received on ATE:Port2 should be equal to the + packets generated from ATE:Port1. +* TTL for all packets received on ATE:Port2 should be *9*. + +### PF-1.9.2: IPv6 non-encapsulated traffic with TTL = 10. + +* Repeat `PF-1.9.1` with ATE generating IPv6 packets ATE:Port2:IPv6. + +### PF-1.9.3: IPv4 non-encapsulated traffic with TTL = 1. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to ATE:Port2:IPv4. + * Set TTL of all packets to *1*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* ATE:Port1 received ICMP TTL Exceeded packets for all packets sent. + +### PF-1.9.4: IPv6 non-encapsulated traffic with TTL = 1. + +* Repeat `PF-1.9.3` with ATE generating IPv6 packets ATE:Port2:IPv6. + +### PF-1.9.5: IPv4oGRE traffic with inner TTL = 10 and outer TTL = 30. + +ATE action: + +* Generate 5 **IPv4oGRE packets** from ATE:Port1 with below headers. + * Set the inner IP header destination to ATE:Port2:IPv4. + * Set the inner IP header TTL to *10*. + * Set the outer IP header destination to IPv4-DST-DECAP/32. + * Set the outer IP header TTL to *30*. + +Verify: + +* Perform same verifications in `PF-1.9.1`. +* Verify that decapsulation rule counters match the number of + packets from ATE:Port1. + +### PF-1.9.6: IPv6oGRE traffic with inner TTL = 10 and outer TTL = 30. + +* Repeat `PF-1.9.5` using IPv6oGRE with inner header destination IP of + ATE:Port2:IPv6. + +### PF-1.9.7: IPv4oGRE traffic with inner TTL = 1 and outer TTL = 30. + +ATE action: + +* Generate 5 **IPv4oGRE packets** from ATE:Port1 with below header settings. + * Set the inner IP header destination to ATE:Port2:IPv4. + * Set the inner IP header TTL to *1*. + * Set the outer IP header destination to IPv4-DST-DECAP/32. + * Set the outer IP header TTL to *30*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* ATE:Port1 received ICMP TTL Exceeded packets for all packets sent. + +### PF-1.9.8: IPv6oGRE traffic with inner TTL = 1 and outer TTL = 30. + +* Repeat `PF-1.9.7` using IPv6oGRE with inner header destination IP of + ATE:Port2:IPv6. + +### PF-1.9.9: IPv4oGRE traffic with inner TTL = 10 and outer TTL = 1. + +ATE action: + +* Generate 5 **IPv4oGRE packets** from ATE:Port1 with below header settings. + * Set the inner IP header destination to ATE:Port2:IPv4. + * Set the inner IP header TTL to *10*. + * Set the outer IP header destination to IPv4-DST-DECAP/32. + * Set the outer IP header TTL to *1*. + +Verify: + +* Perform same verifications in `PF-1.9.1`. +* Verify that decapsulation rules counter match number of + packets from ATE:Port1. + +### PF-1.9.10: IPv6oGRE traffic with inner TTL = 10 and outer TTL = 1. + +* Repeat `PF-1.9.9` using IPv6oGRE with inner header destination IP of + ATE:Port2:IPv6. + +### PF-1.9.11: IPv4oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30. + +ATE action: + +* Generate 5 **IPv4oMPLSoGRE packets** from ATE:Port1 with below headers settings. + * Set the inner IP header destination to ATE:Port2:IPv4. + * Set the inner IP header TTL to *10*. + * Set the MPLS label to *100010*. + * Set the MPLS TTL to *20*. + * Set the outer IP header destination to IPv4-DST-DECAP/32. + * Set the outer IP header TTL to *30*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* DUT interface DUT:Port2 `out-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* The packet count of traffic received on ATE:Port2 should be equal to the + packets generated from ATE:Port1. +* Verify that decapsulation rule counters match number of packets from ATE:Port1. +* TTL for all packets received on ATE:Port2 should *10*. + +### PF-1.9.12: IPv6oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30. + +* Repeat `PF-1.9.11` using IPv6oMPLSoGRE with inner header destination IP of + ATE:Port2:IPv6 and MPLS label of 100020. + +### PF-1.9.13: IPv4oMPLSoGRE traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30. + +ATE action: + +* Generate 5 **IPv4oMPLSoGRE packets** from ATE:Port1 with below headers settings. + * Set the inner IP header destination to ATE:Port2:IPv4. + * Set the inner IP header TTL to *1*. + * Set the MPLS label to *100010*. + * Set the MPLS TTL to *20*. + * Set the outer IP header destination to IPv4-DST-DECAP/32. + * Set the outer IP header TTL to *30*. + +Verify: + +* Perform same verifications in `PF-1.9.11`. + +### PF-1.9.14: IPv6oMPLSoGRE traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30. + +* Repeat `PF-1.9.11` using IPv6oMPLSoGRE with inner header destination IP of + ATE:Port2:IPv6 and MPLS label of 100020. + +### PF-1.9.15: IPv4oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30. + +ATE action: + +* Generate 5 **IPv4oMPLSoGRE packets** from ATE:Port1 with below headers settings. + * Set the inner IP header destination to ATE:Port2:IPv4. + * Set the inner IP header TTL to *10*. + * Set the MPLS label to *100010*. + * Set the MPLS TTL to *1*. + * Set the outer IP header destination to IPv4-DST-DECAP/32. + * Set the outer IP header TTL to *30*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* ATE:Port1 received ICMP TTL Exceeded packets for all packets sent. + +### PF-1.9.16: IPv6oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30. + +* Repeat `PF-1.9.15` using IPv6oMPLSoGRE with inner header destination IP of + ATE:Port2:IPv6 and MPLS label of 100020. + +### PF-1.9.17: IPv4oUDP traffic with inner TTL = 10 and outer TTL = 30. + +* Repeat `PF-1.9.5` using IPv4oUDP (GUE Variant 1) + +### PF-1.9.18: IPv6oUDP traffic with inner TTL = 10 and outer TTL = 30. + +* Repeat `PF-1.9.6` using IPv6oUDP (GUE Variant 1) + +### PF-1.9.19: IPv4oUDP traffic with inner TTL = 1 and outer TTL = 30. + +* Repeat `PF-1.9.7` using IPv4oUDP (GUE Variant 1) + +### PF-1.9.20: IPv6oUDP traffic with inner TTL = 1 and outer TTL = 30. + +* Repeat `PF-1.9.8` using IPv6oUDP (GUE Variant 1) + +### PF-1.9.21: IPv4oUDP traffic with inner TTL = 10 and outer TTL = 1. + +* Repeat `PF-1.9.9` using IPv4oUDP (GUE Variant 1) + +### PF-1.9.22: IPv6oUDP traffic with inner TTL = 10 and outer TTL = 1. + +* Repeat `PF-1.9.10` using IPv6oUDP (GUE Variant 1) + +### PF-1.9.23: IPv4oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30. + +* Repeat `PF-1.9.11` using IPv4oMPLSoUDP (GUE Variant 1) + +### PF-1.9.24: IPv6oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30. + +* Repeat `PF-1.9.12` using IPv6oMPLSoUDP (GUE Variant 1) + +### PF-1.9.25: IPv4oMPLSoUDP traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30. + +* Repeat `PF-1.9.13` using IPv4oMPLSoUDP (GUE Variant 1) + +### PF-1.9.26: IPv6oMPLSoUDP traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30. + +* Repeat `PF-1.9.14` using IPv6oMPLSoUDP (GUE Variant 1) + +### PF-1.9.27: IPv4oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30. + +* Repeat `PF-1.9.15` using IPv6oMPLSoUDP (GUE Variant 1) + +### PF-1.9.28: IPv6oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30. + +* Repeat `PF-1.9.16` using IPv6oMPLSoUDP (GUE Variant 1) + +## Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "config": { + "name": "DEFAULT" + }, + "mpls": { + "lsps": { + "static-lsps": { + "static-lsp": [ + { + "config": { + "name": "ipv4-static-lsp" + } + "egress": { + "config": { + "incoming-label": 100010 + "next-hop": "192.168.20.1" + } + } + }, + { + "config": { + "name": "ipv6-static-lsp" + } + "egress": { + "config": { + "incoming-label": 100020 + "next-hop": "2001:DB8::192:168:20:1" + } + } + } + ] + } + } + } + "policy-forwarding": { + "policies": { + "policy": [ + { + "config": { + "policy-id": "gre_decap" + }, + "rules": { + "rule": [ + { + "sequence-id": 0 + "config": { + "sequence-id": 0 + } + "ipv4": { + "config": { + "destination-address": "10.2.2.2" + "protocol": IP_GRE + } + }, + "action": { + "config": { + "decapsulate-gre": true + } + } + }, + { + "sequence-id": 1 + "config": { + "sequence-id": 1 + } + "ipv4": { + "config": { + "destination-address": "10.2.2.2" + "protocol": IP_UDP + } + }, + "transport": { + "config": { + "destination-port": "6080" + } + }, + "action": { + "config": { + "decapsulate-gue": true + } + } + }, + { + "sequence-id": 2 + "config": { + "sequence-id": 2 + } + "ipv4": { + "config": { + "destination-address": "10.2.2.2" + "protocol": IP_UDP + } + }, + "transport": { + "config": { + "destination-port": "6635" + } + }, + "action": { + "config": { + "decapsulate-mpls-in-udp": true + } + } + } + ] + } + }, + ] + } + }, + } + ] + } +} +``` + + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config + /network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/config/sequence-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/destination-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/protocol: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/transport/config/destination-port: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-gre: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-gue: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/decapsulate-mpls-in-udp: + + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/config/incoming-label: + /network-instances/network-instance/mpls/lsps/static-lsps/static-lsp/egress/config/next-hop: + + # Telemetry + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-pkts: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-octets: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + diff --git a/feature/ttl/otg_tests/egress/egress_handle_ttl_test.go b/feature/ttl/otg_tests/egress/egress_handle_ttl_test.go new file mode 100644 index 00000000000..3194e9881d0 --- /dev/null +++ b/feature/ttl/otg_tests/egress/egress_handle_ttl_test.go @@ -0,0 +1,941 @@ +// Copyright 2022 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 egress_handle_ttl_test + +import ( + "os" + "testing" + "time" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/cfgplugins" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/otgutils" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/otg" +) + +const ( + ipv4PrefixLen = 30 + ipv6PrefixLen = 126 + ipv4Decap = "10.2.2.2" + greDecapGrpName = "GRE-DECAP" + frameSize = 128 + packetsPerFlow = 5 + pps = 100 // Packets per second + mplsLabelV4 = 99910 // Static Mpls currently supported range 16 - 99999 (99910 instead of 100010) + mplsLabelV6 = 99920 // Static Mpls currently supported range 16 - 99999 (99920 instead of 100020) + sleepTime = 20 + flowname = "trafficItem" + tolerance = 2 + udpDecapPort = 6080 // UDP destination port for GUE-like decapsulation + defaultNI = "DEFAULT" + policyName = "decap-policy" + policyId = 1 + lspName1 = "lsp1" + lspName2 = "lsp2" +) + +var ( + // DUT port attributes + dutPort1 = attrs.Attributes{ + Desc: "DUT Port 1", + IPv4: "192.168.10.2", + IPv6: "2001:db8::192:168:10:2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + dutPort2 = attrs.Attributes{ + Desc: "DUT Port 2", + IPv4: "192.168.20.2", + IPv6: "2001:db8::192:168:20:2", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + + // ATE port attributes + atePort1 = attrs.Attributes{ + Name: "atePort1", + MAC: "02:00:01:01:01:01", + IPv4: "192.168.10.1", + IPv6: "2001:db8::192:168:10:1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + atePort2 = attrs.Attributes{ + Name: "atePort2", + MAC: "02:00:02:01:01:01", + IPv4: "192.168.20.1", + IPv6: "2001:db8::192:168:20:1", + IPv4Len: ipv4PrefixLen, + IPv6Len: ipv6PrefixLen, + } + expectedTTL1 = 9 +) + +type flowArgs struct { + flowName string + outerSrcIP, outerDstIP string + InnerSrcIP, InnerDstIP string + InnerSrcIPv6, InnerDstIPv6 string + ipv4Flow bool + outerIpv4Ttl int + outerIpv6Ttl int +} + +type packetValidation struct { + portName string + outerDstIP string + innerDstIP string + validateDecap bool + validateNonEncap bool + outerTtl int + innerTtl int +} + +func TestMain(m *testing.M) { + fptest.RunTests(m) +} + +func TestEgressHandleTTL(t *testing.T) { + dut := ondatra.DUT(t, "dut") + ate := ondatra.ATE(t, "ate") + configureDUT(t, dut) + config := configureATE(t) + otgConfig := ate.OTG() + sfBatch := &gnmi.SetBatch{} + // Configure Static Route: MPLS label binding + cfgplugins.MPLSStaticLSPByPass(t, sfBatch, dut, lspName1, mplsLabelV4, atePort2.IPv4, "ipv4", true) + cfgplugins.MPLSStaticLSPByPass(t, sfBatch, dut, lspName2, mplsLabelV6, atePort2.IPv6, "ipv6", true) + sfBatch.Set(t, dut) + // Policy Based Forwading Rule-1 + cfgplugins.NewConfigureGRETunnel(t, dut, ipv4Decap, greDecapGrpName) + + // Test cases. + type testCase struct { + Name string + Description string + InnerTTL int + OuterTTL int + MplsTTL int + TestFunc func(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) + } + + testCases := []testCase{ + { + Name: "Testcase-IPv4NonEncapsulatedTraffic", + Description: "IPv4 non-encapsulated traffic with TTL = 10", + InnerTTL: 10, + TestFunc: createIPv4Flow, + }, + { + Name: "Testcase-IPv6NonEncapsulatedTraffic", + Description: "PF-1.9.2: IPv6 non-encapsulated traffic with TTL = 10", + InnerTTL: 10, + TestFunc: createIPv6Flow, + }, + { + Name: "Testcase-NegIPv4NonEncapsulatedTrafficTTL", + Description: "PF-1.9.3: IPv4 non-encapsulated traffic with TTL = 1", + InnerTTL: 1, + TestFunc: createIPv4Flow, + }, + { + Name: "Testcase-NegIPv6NonEncapsulatedTrafficTTL", + Description: "PF-1.9.4: IPv6 non-encapsulated traffic with TTL = 1", + InnerTTL: 1, + TestFunc: createIPv6Flow, + }, + { + Name: "Testcase-IPv4oGreDecapsulatedTrafficTTL", + Description: "PF-1.9.5: IPv4oGRE traffic with inner TTL = 10 and outer TTL = 30", + InnerTTL: 10, + OuterTTL: 30, + TestFunc: createIPv4oGREFlow, + }, + { + Name: "Testcase-IPv6oGreDecapsulatedTrafficTTL", + Description: "PF-1.9.6: IPv6oGRE traffic with inner TTL = 10 and outer TTL = 30", + InnerTTL: 10, + OuterTTL: 30, + TestFunc: createIPv6oGREFlow, + }, + { + Name: "Testcase-InnerIPv4oGreDecapsulatedTrafficTTL", + Description: "PF-1.9.7: IPv4oGRE traffic with inner TTL = 1 and outer TTL = 30", + InnerTTL: 1, + OuterTTL: 30, + TestFunc: createIPv4oGREFlow, + }, + { + Name: "Testcase-InnerIPv6oGreDecapsulatedTrafficTTL", + Description: "PF-1.9.8: IPv6oGRE traffic with inner TTL = 1 and outer TTL = 30", + InnerTTL: 1, + OuterTTL: 30, + TestFunc: createIPv6oGREFlow, + }, + { + Name: "Testcase-OuterIPv4oGreDecapsulatedTrafficTTL", + Description: "PF-1.9.9: IPv4oGRE traffic with inner TTL = 10 and outer TTL = 1", + InnerTTL: 10, + OuterTTL: 1, + TestFunc: createIPv4oGREFlow, + }, + { + Name: "Testcase-OuterIPv6oGreDecapsulatedTrafficTTL", + Description: "PF-1.9.10: IPv6oGRE traffic with inner TTL = 10 and outer TTL = 1", + InnerTTL: 10, + OuterTTL: 1, + TestFunc: createIPv6oGREFlow, + }, + { + Name: "Testcase-IPv4oMPLSoGRETest", + Description: "PF-1.9.11: IPv4oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv4oMPLSoGREFlow, + }, + { + Name: "Testcase-IPv6oMPLSoGRETest", + Description: "PF-1.9.12: IPv6oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv6oMPLSoGREFlow, + }, + { + Name: "Testcase-IPv4oMPLSoGREInnerTest", + Description: "PF-1.9.13: IPv4oMPLSoGRE traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 1, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv4oMPLSoGREFlow, + }, + { + Name: "Testcase-IPv6oMPLSoGREInnerTest", + Description: "PF-1.9.14: IPv6oMPLSoGRE traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 1, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv6oMPLSoGREFlow, + }, + { + Name: "Testcase-IPv4oMPLSoGREMplsTest", + Description: "PF-1.9.15: IPv4oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 1, + OuterTTL: 30, + TestFunc: createIPv4oMPLSoGREFlow, + }, + { + Name: "Testcase-IPv6oMPLSoGREMplsTest", + Description: "PF-1.9.16: IPv6oMPLSoGRE traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 1, + OuterTTL: 30, + TestFunc: createIPv6oMPLSoGREFlow, + }, + { + Name: "Testcase-IPv4oUDPTest", + Description: "PF-1.9.17: IPv4oUDP traffic with inner TTL = 10 and outer TTL = 30", + InnerTTL: 10, + OuterTTL: 30, + TestFunc: createIPv4oUDPFlow, + }, + { + Name: "Testcase-IPv6oUDPTest", + Description: "PF-1.9.18: IPv6oUDP traffic with inner TTL = 10 and outer TTL = 30", + InnerTTL: 10, + OuterTTL: 30, + TestFunc: createIPv6oUDPFlow, + }, + { + Name: "Testcase-IPv4oUDPInnerTest", + Description: "PF-1.9.19: IPv4oUDP traffic with inner TTL = 1 and outer TTL = 30", + InnerTTL: 1, + OuterTTL: 30, + TestFunc: createIPv4oUDPFlow, + }, + { + Name: "Testcase-IPv6oUDPInnerTest", + Description: "PF-1.9.20: IPv6oUDP traffic with inner TTL = 1 and outer TTL = 30", + InnerTTL: 1, + OuterTTL: 30, + TestFunc: createIPv6oUDPFlow, + }, + { + Name: "Testcase-IPv4oUDPOuterTest", + Description: "PF-1.9.21: IPv4oUDP traffic with inner TTL = 10 and outer TTL = 1", + InnerTTL: 10, + OuterTTL: 1, + TestFunc: createIPv4oUDPFlow, + }, + { + Name: "Testcase-IPv4oUDPOuterTest", + Description: "PF-1.9.22: IPv6oUDP traffic with inner TTL = 10 and outer TTL = 1", + InnerTTL: 10, + OuterTTL: 1, + TestFunc: createIPv6oUDPFlow, + }, + { + Name: "Testcase-IPv4oMPLSoUDPTest", + Description: "PF-1.9.23: IPv4oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv4oMPLSoUDPFlow, + }, + { + Name: "Testcase-IPv6oMPLSoUDPTest", + Description: "PF-1.9.24: IPv6oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv6oMPLSoUDPFlow, + }, + { + Name: "Testcase-IPv4oMPLSoUDPInnerTest", + Description: "PF-1.9.25: IPv4oMPLSoUDP traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 1, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv4oMPLSoUDPFlow, + }, + { + Name: "Testcase-IPv6oMPLSoUDPInnerTest", + Description: "PF-1.9.26: IPv6oMPLSoUDP traffic with inner TTL = 1, MPLS TTL = 20 and outer TTL = 30", + InnerTTL: 1, + MplsTTL: 20, + OuterTTL: 30, + TestFunc: createIPv6oMPLSoUDPFlow, + }, + { + Name: "Testcase-IPv4oMPLSoUDPMplsTest", + Description: "PF-1.9.27: IPv4oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 1, + OuterTTL: 30, + TestFunc: createIPv4oMPLSoUDPFlow, + }, + { + Name: "Testcase-IPv6oMPLSoUDPMplsTest", + Description: "PF-1.9.28: IPv6oMPLSoUDP traffic with inner TTL = 10, MPLS TTL = 1 and outer TTL = 30", + InnerTTL: 10, + MplsTTL: 1, + OuterTTL: 30, + TestFunc: createIPv6oMPLSoUDPFlow, + }, + } + + // Run the test cases. + for _, tc := range testCases { + t.Run(tc.Name, func(t *testing.T) { + t.Logf("Description: %s", tc.Description) + tc.TestFunc(t, dut, otgConfig, config, tc.InnerTTL, tc.OuterTTL, tc.MplsTTL) + }) + } +} + +func configureDUT(t *testing.T, dut *ondatra.DUTDevice) { + t.Helper() + d := gnmi.OC() + // Configure interfaces + p1 := dut.Port(t, "port1").Name() + i1 := dutPort1.NewOCInterface(p1, dut) + gnmi.Replace(t, dut, d.Interface(p1).Config(), i1) + + p2 := dut.Port(t, "port2").Name() + i2 := dutPort2.NewOCInterface(p2, dut) + gnmi.Replace(t, dut, d.Interface(p2).Config(), i2) + + fptest.ConfigureDefaultNetworkInstance(t, dut) +} + +func configureATE(t *testing.T) gosnappi.Config { + t.Helper() + t.Log("Configure ATE interfaces with BGP sessions and routes") + + config := gosnappi.NewConfig() + + // Add ports + port1 := config.Ports().Add().SetName("port1") + port2 := config.Ports().Add().SetName("port2") + + // Configure port1 + configureATEPorts(t, config, port1, atePort1, dutPort1) + // Configure port2 + configureATEPorts(t, config, port2, atePort2, dutPort2) + + return config +} + +func configureATEPorts(t *testing.T, config gosnappi.Config, port gosnappi.Port, ate attrs.Attributes, dut attrs.Attributes) { + t.Helper() + dev := config.Devices().Add().SetName(ate.Name + ".dev") + + eth := dev.Ethernets().Add(). + SetName(ate.Name + ".Eth"). + SetMac(ate.MAC) + eth.Connection().SetPortName(port.Name()) + + ipv4 := eth.Ipv4Addresses().Add().SetName(ate.Name + ".IPv4") + ipv4.SetAddress(ate.IPv4).SetGateway(dut.IPv4).SetPrefix(uint32(ate.IPv4Len)) + + ipv6 := eth.Ipv6Addresses().Add().SetName(ate.Name + ".IPv6") + ipv6.SetAddress(ate.IPv6).SetGateway(dut.IPv6).SetPrefix(uint32(ate.IPv6Len)) +} + +func addFlow(t *testing.T, config gosnappi.Config, flowValues *flowArgs) gosnappi.Flow { + t.Helper() + dut := ondatra.DUT(t, "dut") + macAddress := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Ethernet().MacAddress().State()) + flow := gosnappi.NewFlow().SetName(flowValues.flowName) + flow.Metrics().SetEnable(true) + flow.TxRx().Port().SetTxName(config.Ports().Items()[0].Name()).SetRxNames([]string{config.Ports().Items()[1].Name()}) + flow.Size().SetFixed(frameSize) + flow.Duration().FixedPackets().SetPackets(packetsPerFlow) + flow.Rate().SetPps(pps) + ethHeader := flow.Packet().Add().Ethernet() + ethHeader.Src().SetValue(atePort1.MAC) + ethHeader.Dst().SetValue(macAddress) + if flowValues.ipv4Flow { + ipv4Header := flow.Packet().Add().Ipv4() + ipv4Header.Src().SetValue(flowValues.outerSrcIP) + ipv4Header.Dst().SetValue(flowValues.outerDstIP) + ipv4Header.TimeToLive().SetValue(uint32(flowValues.outerIpv4Ttl)) + } else { + ipv6Header := flow.Packet().Add().Ipv6() + ipv6Header.Src().SetValue(flowValues.outerSrcIP) + ipv6Header.Dst().SetValue(flowValues.outerDstIP) + ipv6Header.HopLimit().SetValue(uint32(flowValues.outerIpv6Ttl)) + } + + return flow +} + +// verifyTrafficFlow verify the each flow on ATE +func verifyTrafficFlow(t *testing.T, otgConfig *otg.OTG, config gosnappi.Config, flow gosnappi.Flow) bool { + t.Helper() + otgutils.LogFlowMetrics(t, otgConfig, config) + rxPkts := gnmi.Get(t, otgConfig, gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State()) + txPkts := gnmi.Get(t, otgConfig, gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State()) + lostPkt := txPkts - rxPkts + if txPkts == 0 { + t.Fatalf("txPkts == %d, want > 0.", txPkts) + } + if got := (lostPkt * 100 / txPkts); got >= tolerance { + return false + } + return true +} + +func captureAndValidatePackets(t *testing.T, otgConfig *otg.OTG, packetVal *packetValidation, protocolType string) { + t.Helper() + packetCaptureGRE := processCapture(t, otgConfig, "port2") + handle, err := pcap.OpenOffline(packetCaptureGRE) + if err != nil { + t.Fatal(err) + } + defer handle.Close() + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + if packetVal.validateDecap { + validateTrafficDecap(t, packetSource, packetVal.innerDstIP, packetVal.innerTtl, protocolType) + } + if packetVal.validateNonEncap { + validateTrafficNonEncap(t, packetSource, packetVal.outerDstIP, packetVal.outerTtl, protocolType) + } +} + +func validateTrafficNonEncap(t *testing.T, packetSource *gopacket.PacketSource, expectedIP string, expectedTTL int, protocol string) { + t.Helper() + t.Logf("Validate non-encapsulated traffic for protocol: %s", protocol) +outer: + for packet := range packetSource.Packets() { + switch protocol { + case "ipv4": + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket := ipLayer.(*layers.IPv4) + gotTTL := ipPacket.TTL + gotDstIP := ipPacket.DstIP.String() + + if gotDstIP == expectedIP && int(gotTTL) == expectedTTL { + t.Logf("Matched IPv4 packet: DstIP = %s, TTL = %d", gotDstIP, gotTTL) + break outer + } else { + t.Errorf("Failed to match IP/TTL, GotIP = %s, Expected IP = %s, GotTTL = %d, ExpectedTTL = %d", gotDstIP, expectedIP, gotTTL, expectedTTL) + break outer + } + + case "ipv6": + ipLayer := packet.Layer(layers.LayerTypeIPv6) + if ipLayer == nil { + continue + } + ipPacket := ipLayer.(*layers.IPv6) + gotHopLimit := ipPacket.HopLimit + gotDstIP := ipPacket.DstIP.String() + if gotDstIP == expectedIP && int(gotHopLimit) == expectedTTL { + t.Logf("Matched IPv6 packet: DstIP = %s, HopLimit = %d", gotDstIP, gotHopLimit) + break outer + } else { + t.Errorf("Failed to match IP/TTL, GotIP = %s, Expected IP = %s, GotHopLimit = %d, ExpectedHopLimit = %d", gotDstIP, expectedIP, gotHopLimit, expectedTTL) + break outer + } + + default: + t.Fatalf("Unsupported protocol type: %s. Must be 'ipv4' or 'ipv6'", protocol) + } + } +} + +func validateTrafficDecap(t *testing.T, packetSource *gopacket.PacketSource, expectedIP string, expectedTTL int, protocol string) { + t.Helper() + t.Log("Validating decapsulated traffic: Inner DstIP and TTL/HopLimit") +outer: + for packet := range packetSource.Packets() { + switch protocol { + case "ipv4": + ipLayer := packet.Layer(layers.LayerTypeIPv4) + if ipLayer == nil { + continue + } + ipPacket := ipLayer.(*layers.IPv4) + payload := ipPacket.Payload + nextLayerType := ipPacket.NextLayerType() + + gotTTL := ipPacket.TTL + gotDstIP := ipPacket.DstIP.String() + if gotDstIP == expectedIP && int(gotTTL) == expectedTTL { + t.Logf("Matched IPv4 packet: DstIP = %s, TTL = %d", gotDstIP, gotTTL) + + // Decode inner packet from outer payload + innerPacket := gopacket.NewPacket(payload, nextLayerType, gopacket.Default) + if innerPacket.Layer(layers.LayerTypeIPv4) != nil { + t.Errorf("Packets are not decapped: inner IPv4 header still present.") + } + break outer + } else { + t.Errorf("Failed to match IP/TTL, GotIP = %s, Expected IP = %s, GotTTL = %d, ExpectedTTL = %d", gotDstIP, expectedIP, gotTTL, expectedTTL) + break outer + } + + case "ipv6": + ipLayer := packet.Layer(layers.LayerTypeIPv6) + if ipLayer == nil { + continue + } + ipPacket := ipLayer.(*layers.IPv6) + payload := ipPacket.Payload + nextLayerType := ipPacket.NextLayerType() + + gotHopLimit := ipPacket.HopLimit + gotDstIP := ipPacket.DstIP.String() + + if gotDstIP == expectedIP && int(gotHopLimit) == expectedTTL { + t.Logf("Matched IPv6 packet: DstIP = %s, HopLimit = %d", gotDstIP, gotHopLimit) + + // Decode inner packet from outer payload + innerPacket := gopacket.NewPacket(payload, nextLayerType, gopacket.Default) + if innerPacket.Layer(layers.LayerTypeIPv6) != nil { + t.Errorf("Packets are not decapped: inner IPv6 header still present.") + } + break outer + } else { + t.Errorf("Failed to match IP/TTL, GotIP = %s, Expected IP = %s, GotHopLimit = %d, ExpectedHopLimit = %d", gotDstIP, expectedIP, gotHopLimit, expectedTTL) + break outer + } + + default: + t.Fatalf("Unsupported protocol type: %s. Must be 'ipv4' or 'ipv6'", protocol) + } + } +} + +func validateDUTPkts(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, flow gosnappi.Flow, initialInUnicastPkts, initialOutUnicastPkts, finalInUnicastPkts, finalOutUnicastPkts uint64) { + t.Helper() + if deviations.GreDecapsulationOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + ingressPkt := finalInUnicastPkts - initialInUnicastPkts + ingressAtePkts := gnmi.Get(t, otgConfig, gnmi.OTG().Flow(flow.Name()).Counters().OutPkts().State()) + + egressPkt := finalOutUnicastPkts - initialOutUnicastPkts + egressAtePkts := gnmi.Get(t, otgConfig, gnmi.OTG().Flow(flow.Name()).Counters().InPkts().State()) + + if ingressPkt == 0 || egressPkt == 0 { + t.Errorf("Got the unexpected packet count ingressPkt: %d, egressPkt: %d", ingressPkt, egressPkt) + } + + if ingressPkt >= ingressAtePkts && egressPkt >= egressAtePkts { + t.Logf("Interface counters reflect decapsulated packets: InUnicastPkts : %d OutUnicastPkts : %d", ingressPkt, egressPkt) + } else { + t.Errorf("Error: Interface counters didn't reflect decapsulated packets.") + } + default: + t.Errorf("Deviation GreDecapsulationUnsupported is not handled for the dut: %v", dut.Vendor()) + } + } else { + // TO-DO: Once the support is added in the DUT, need to work on the validation of PF counters. + matchedPkts := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy("PBR-MAP").Rule(10).MatchedPkts() + pktCount := gnmi.Get(t, dut, matchedPkts.State()) + if pktCount != 0 { + t.Logf("Interface counters received") + } else { + t.Errorf("Interface counters not received") + } + + matchedOctets := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).PolicyForwarding().Policy("PBR-MAP").Rule(10).MatchedOctets() + octetCount := gnmi.Get(t, dut, matchedOctets.State()) + if octetCount == 0 { + t.Errorf("Octet counters not received") + } + } +} + +func otgOperation(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, flow gosnappi.Flow) { + t.Helper() + enableCapture(t, config, "port2") + otgConfig.PushConfig(t, config) + otgConfig.StartProtocols(t) + + verifyPortsUp(t, dut.Device) + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + initialInUnicastPkts := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Counters().InUnicastPkts().State()) + initialOutUnicastPkts := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port2").Name()).Counters().OutUnicastPkts().State()) + + cs := startCapture(t, otgConfig) + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + otgConfig.StopTraffic(t) + + stopCapture(t, otgConfig, cs) + finalInUnicastPkts := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port1").Name()).Counters().InUnicastPkts().State()) + finalOutUnicastPkts := gnmi.Get(t, dut, gnmi.OC().Interface(dut.Port(t, "port2").Name()).Counters().OutUnicastPkts().State()) + validateDUTPkts(t, dut, otgConfig, flow, initialInUnicastPkts, initialOutUnicastPkts, finalInUnicastPkts, finalOutUnicastPkts) + if ok := verifyTrafficFlow(t, otgConfig, config, flow); !ok { + t.Fatal("Packets Dropped, LossPct for flow ") + } else { + t.Log("Packets Received") + } +} + +func otgTrafficValidation(t *testing.T, otgConfig *otg.OTG, config gosnappi.Config, flow gosnappi.Flow) { + t.Helper() + otgConfig.PushConfig(t, config) + otgConfig.StartProtocols(t) + + otgutils.WaitForARP(t, otgConfig, config, "IPv4") + + otgConfig.StartTraffic(t) + time.Sleep(sleepTime * time.Second) + otgConfig.StopTraffic(t) + + if ok := verifyTrafficFlow(t, otgConfig, config, flow); !ok { + t.Log("Packets Dropped, Test Passed") + } else { + t.Fatal("Packet not Dropped, Test Failed") + } +} + +func createIPv4Flow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv4", + outerSrcIP: atePort1.IPv4, outerDstIP: atePort2.IPv4, outerIpv4Ttl: innerTTL, ipv4Flow: true}) + config.Flows().Append(flow) + if innerTTL != 1 { + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + outerDstIP: atePort2.IPv4, outerTtl: expectedTTL1, validateNonEncap: true}, "ipv4") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv6Flow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv6", + outerSrcIP: atePort1.IPv6, outerDstIP: atePort2.IPv6, outerIpv6Ttl: innerTTL}) + config.Flows().Append(flow) + if innerTTL != 1 { + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + outerDstIP: atePort2.IPv6, outerTtl: expectedTTL1, validateNonEncap: true}, "ipv6") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv4oGREFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv4", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + flow.Packet().Add().Gre() + innerv4Header := flow.Packet().Add().Ipv4() + innerv4Header.Src().SetValue(atePort1.IPv4) + innerv4Header.Dst().SetValue(atePort2.IPv4) + innerv4Header.TimeToLive().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + + if innerTTL != 1 { + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv4, innerTtl: expectedTTL1, validateDecap: true}, "ipv4") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv6oGREFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv6", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + flow.Packet().Add().Gre() + innerv6Header := flow.Packet().Add().Ipv6() + innerv6Header.Src().SetValue(atePort1.IPv6) + innerv6Header.Dst().SetValue(atePort2.IPv6) + innerv6Header.HopLimit().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + if innerTTL != 1 { + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv6, innerTtl: expectedTTL1, validateDecap: true}, "ipv6") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv4oMPLSoGREFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv4", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + flow.Packet().Add().Gre() + mplsHeader := flow.Packet().Add().Mpls() + mplsHeader.Label().SetValue(mplsLabelV4) + mplsHeader.TimeToLive().SetValue(uint32(mplsTTL)) + innerv4Header := flow.Packet().Add().Ipv4() + innerv4Header.Src().SetValue(atePort1.IPv4) + innerv4Header.Dst().SetValue(atePort2.IPv4) + innerv4Header.TimeToLive().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + expectedTTL2 := 10 + if mplsTTL != 1 { + if innerTTL == 1 { + expectedTTL2 = 1 + } + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv4, innerTtl: expectedTTL2, validateDecap: true}, "ipv4") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv6oMPLSoGREFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv6", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + flow.Packet().Add().Gre() + mplsHeader := flow.Packet().Add().Mpls() + mplsHeader.Label().SetValue(mplsLabelV6) + mplsHeader.TimeToLive().SetValue(uint32(mplsTTL)) + innerv6Header := flow.Packet().Add().Ipv6() + innerv6Header.Src().SetValue(atePort1.IPv6) + innerv6Header.Dst().SetValue(atePort2.IPv6) + innerv6Header.HopLimit().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + expectedTTL2 := 10 + if mplsTTL != 1 { + if innerTTL == 1 { + expectedTTL2 = 1 + } + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv6, innerTtl: expectedTTL2, validateDecap: true}, "ipv6") + + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv4oUDPFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + dp1 := dut.Port(t, "port1") + cfgplugins.ConfigureDutWithGueDecap(t, dut, udpDecapPort, "ipv4", ipv4Decap, dp1.Name(), policyName, policyId) + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv4", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + udpHeader := flow.Packet().Add().Udp() + udpHeader.DstPort().SetValue(udpDecapPort) + innerv4Header := flow.Packet().Add().Ipv4() + innerv4Header.Src().SetValue(atePort1.IPv4) + innerv4Header.Dst().SetValue(atePort2.IPv4) + innerv4Header.TimeToLive().SetValue(uint32(innerTTL)) + + config.Flows().Append(flow) + if innerTTL != 1 { + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv4, innerTtl: expectedTTL1, validateDecap: true}, "ipv4") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv6oUDPFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + dp1 := dut.Port(t, "port1") + cfgplugins.ConfigureDutWithGueDecap(t, dut, udpDecapPort, "ipv6", ipv4Decap, dp1.Name(), policyName, policyId) + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv6", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + udpHeader := flow.Packet().Add().Udp() + udpHeader.DstPort().SetValue(udpDecapPort) + innerv6Header := flow.Packet().Add().Ipv6() + innerv6Header.Src().SetValue(atePort1.IPv6) + innerv6Header.Dst().SetValue(atePort2.IPv6) + innerv6Header.HopLimit().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + if innerTTL != 1 { + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv6, innerTtl: expectedTTL1, validateDecap: true}, "ipv6") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv4oMPLSoUDPFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + dp1 := dut.Port(t, "port1") + cfgplugins.ConfigureDutWithGueDecap(t, dut, udpDecapPort, "mpls", ipv4Decap, dp1.Name(), policyName, policyId) + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv4", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + udpHeader := flow.Packet().Add().Udp() + udpHeader.DstPort().SetValue(udpDecapPort) + mplsHeader := flow.Packet().Add().Mpls() + mplsHeader.Label().SetValue(mplsLabelV4) + mplsHeader.TimeToLive().SetValue(uint32(mplsTTL)) + innerv4Header := flow.Packet().Add().Ipv4() + innerv4Header.Src().SetValue(atePort1.IPv4) + innerv4Header.Dst().SetValue(atePort2.IPv4) + innerv4Header.TimeToLive().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + expectedTTL2 := 10 + if mplsTTL != 1 { + if innerTTL == 1 { + expectedTTL2 = 1 + } + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv4, innerTtl: expectedTTL2, validateDecap: true}, "ipv4") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func createIPv6oMPLSoUDPFlow(t *testing.T, dut *ondatra.DUTDevice, otgConfig *otg.OTG, config gosnappi.Config, innerTTL, outerTTL, mplsTTL int) { + t.Helper() + config.Flows().Clear() + flow := addFlow(t, config, &flowArgs{flowName: flowname + "-ipv6", + outerSrcIP: atePort1.IPv4, outerDstIP: ipv4Decap, outerIpv4Ttl: outerTTL, ipv4Flow: true}) + udpHeader := flow.Packet().Add().Udp() + udpHeader.DstPort().SetValue(udpDecapPort) + mplsHeader := flow.Packet().Add().Mpls() + mplsHeader.Label().SetValue(mplsLabelV6) + mplsHeader.TimeToLive().SetValue(uint32(mplsTTL)) + innerv6Header := flow.Packet().Add().Ipv6() + innerv6Header.Src().SetValue(atePort1.IPv6) + innerv6Header.Dst().SetValue(atePort2.IPv6) + innerv6Header.HopLimit().SetValue(uint32(innerTTL)) + config.Flows().Append(flow) + expectedTTL2 := 10 + if mplsTTL != 1 { + if innerTTL == 1 { + expectedTTL2 = 1 + } + otgOperation(t, dut, otgConfig, config, flow) + captureAndValidatePackets(t, otgConfig, &packetValidation{portName: atePort2.Name, + innerDstIP: atePort2.IPv6, innerTtl: expectedTTL2, validateDecap: true}, "ipv6") + } else { + otgTrafficValidation(t, otgConfig, config, flow) + } +} + +func enableCapture(t *testing.T, config gosnappi.Config, port string) { + t.Helper() + config.Captures().Clear() + t.Log("Enabling capture on ", port) + config.Captures().Add().SetName(port).SetPortNames([]string{port}).SetFormat(gosnappi.CaptureFormat.PCAP) +} + +func startCapture(t *testing.T, otg *otg.OTG) gosnappi.ControlState { + t.Helper() + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + otg.SetControlState(t, cs) + + return cs +} + +func stopCapture(t *testing.T, otg *otg.OTG, cs gosnappi.ControlState) { + t.Helper() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + otg.SetControlState(t, cs) +} + +func processCapture(t *testing.T, otg *otg.OTG, port string) string { + t.Helper() + bytes := otg.GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(port)) + time.Sleep(30 * time.Second) + capturePktFile, err := os.CreateTemp("", "pcap") + if err != nil { + t.Errorf("ERROR: Could not create temporary pcap file: %v\n", err) + } + if _, err := capturePktFile.Write(bytes); err != nil { + t.Errorf("ERROR: Could not write bytes to pcap file: %v\n", err) + } + defer capturePktFile.Close() // <- ensures the file is always closed + return capturePktFile.Name() +} + +// Verify ports status +func verifyPortsUp(t *testing.T, dev *ondatra.Device) { + t.Helper() + t.Log("Verifying port status") + for _, p := range dev.Ports() { + status := gnmi.Get(t, dev, gnmi.OC().Interface(p.Name()).OperStatus().State()) + if want := oc.Interface_OperStatus_UP; status != want { + t.Errorf("%s Status: got %v, want %v", p, status, want) + } + } +} diff --git a/feature/ttl/otg_tests/egress/metadata.textproto b/feature/ttl/otg_tests/egress/metadata.textproto new file mode 100644 index 00000000000..9afa71c4cbd --- /dev/null +++ b/feature/ttl/otg_tests/egress/metadata.textproto @@ -0,0 +1,19 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "60e4c1d1-a36e-49af-bd10-188a626b84bf" +plan_id: "PF-1.9" +description: "Egress handling of TTL" +testbed: TESTBED_DUT_ATE_2LINKS +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + static_mpls_lsp_oc_unsupported: true + gre_decapsulation_oc_unsupported: true + decapsulate_gue_oc_unsupported: true + } +} \ No newline at end of file diff --git a/feature/ttl/otg_tests/ingress/README.md b/feature/ttl/otg_tests/ingress/README.md new file mode 100644 index 00000000000..8278f205dd4 --- /dev/null +++ b/feature/ttl/otg_tests/ingress/README.md @@ -0,0 +1,271 @@ +# PF-1.8: Ingress handling of TTL + +## Summary + +This test verifies TTL handling for ingress flows. + +## Testbed Type + +* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) + +## Procedure + +### Test environment setup + +* DUT has one ingress port and one egress port, both connected to ATE. + + ``` + ------- + | | + [ ATE:Port1 ] ---- | DUT | ---- [ ATE:Port2 ] + | | + ------- + ``` + +* Traffic is generated from ATE:Port1. +* ATE:Port2 is used as the destination port for flows. + +#### Configuration + +1. DUT:Port1 is configured as Singleton IP interface towards ATE:Port1 + with IPv4 and IP6 addresses. + +2. DUT:Port2 is configured as Singleton IP interface towards ATE:Port2 + with IPv4 and IP6 addresses. + +3. DUT is configured with the following static routes: + a) Destination IPv4-DST-NET/32 next hop ATE:Port2 IPv4 address. + b) Destination IPv6-DST-NET/32 next hop ATE:Port2 IPv6 address. + c) Destination IPv4-DST-NET-SERV1/32 next hop ATE:Port2 IPv4 address. + d) Destination IPv6-DST-NET-SERV1/32 next hop ATE:Port2 IPv6 address. + c) Destination GRE-IPv4-DST-NET/32 next hop ATE:Port2 IPv4 address. + +### PF-1.8.1: IPv4 traffic with no encapsulation on DUT and TTL = 10. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to IPv4-DST-NET/32. + * Set TTL of all packets to *10*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* DUT interface DUT:Port2 `out-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* The packet count of traffic received on ATE:Port2 should be equal to the + packets generated from ATE:Port1. +* For ONLY non-encapsulated packets; TTL for all packets received on ATE:Port2 + should be *9*. + +### PF-1.8.2: IPv6 traffic with no encapsulation on DUT and TTL = 10. + +* Repeat `PF-1.8.1` with ATE generating IPv6 packets IPv6-DST-NET/128. + +### PF-1.8.3: IPv4 traffic with no encapsulation on DUT and TTL = 1. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to IPv4-DST-NET/32. + * Set TTL of all packets to *1*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* ATE:Port1 received ICMP TTL Exceeded packets for all packets sent. + +### PF-1.8.4: IPv6 traffic with no encapsulation on DUT and TTL = 1. + +* Repeat `PF-1.8.3` with ATE generating IPv6 packets IPv6-DST-NET/128. + +### PF-1.8.5: IPv4 traffic with GRE encapsulation on DUT and TTL = 10. + +DUT action: + +* Configure DUT for GRE encapsulation as follows. + * Packets destined to IPv4-DST-NET-SERV1/32 should be GRE encapsulated + to GRE-IPv4-DST-NET/32 with outer IP having TTL = 64. + * Packets destined to IPv6-DST-NET-SERV1/128 should be GRE encapsulated + to GRE-IPv4-DST-NET/32 with outer IP having TTL = 64. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to IPv4-DST-NET-SERV1/32. + * Set TTL of all packets to *10*. + +Verify: + +* Perform same verifications in `PF-1.8.1`. + * TTL for inner IP is 10. + * TTL for outer IP is 64. + * In addition, verify that encapsulation rules counter match number of + packets from ATE:Port1. + +### PF-1.8.6: IPv6 traffic with GRE encapsulation on DUT and TTL = 10. + +* Repeat `PF-1.8.5` with ATE generating IPv6 packets IPv6-DST-NET-SERV1/128. + +### PF-1.8.7: IPv4 traffic with GRE encapsulation on DUT and TTL = 1 with DUT configured to process TTL = 1 on receiving interface. + +DUT action: + +* Additional configuration on DUT + * Packets with TTL = 1 should be processed locally first before encapsulation. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to IPv4-DST-NET-SERV1/32. + * Set TTL of all packets to *1*. + +Verify: + +* DUT interface DUT:Port1 `in-unicast-pkts` counters equals the number of + packets generated from ATE:Port1. +* ATE:Port1 received ICMP Time Exceeded packets for all packets sent. + +### PF-1.8.8: IPv6 traffic with GRE encapsulation on DUT and TTL = 1 with DUT configured to process TTL = 1 on receiving interface. + +* Repeat `PF-1.8.7` with ATE generating IPv6 packets IPv6-DST-NET-SERV1/128. + +### PF-1.8.9: GRE encapsulation of IPv4 traffic with TTL = 1 destined to router interface. + +DUT action: + +* Additional configuration on DUT + * Update GRE encapsulation configuration so that packets with TTL = 1 + destined to the router interface IP should be encapsulated. + +ATE action: + +* Generate 5 **IPv4 packets** from ATE:Port1 to IPv4-DST-NET-SERV1/32. + * Set TTL of all packets to *1*. + +Verify: + +* Perform same verifications in `PF-1.8.5`. + +### PF-1.8.10: GRE encapsulation of IPv6 traffic with TTL = 1 destined to router interface. + +* Repeat `PF-1.8.9` with ATE generating IPv6 packets IPv6-DST-NET-SERV1/128. + +### Canonical OpenConfig for policy-forwarding matching IPv4 and encapsulate GRE + +TODO: New OC paths to be proposed are present in below JSON +* config/rules/rule/action/count: true +* config/rules/rule/action/next-hop-group +* encap-headers/encap-header/type: "GRE" and associated parameters + +#### Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "name": "DEFAULT", + "config": { + "name": "DEFAULT" + }, + "policy-forwarding": { + "interfaces": { + "interface": [ + { + "config": { + "apply-forwarding-policy": "customer1_gre_encap", + "interface-id": "intf1" + }, + "interface-id": "intf1" + } + ] + }, + "policies": { + "policy": [ + { + "config": { + "policy-id": "customer1_gre_encap" + }, + "rules": { + "rule": [ + { + "sequence-id": 0, + "config": { + "sequence-id": 0 + }, + "ipv4": { + "config": { + "destination-address": "192.168.1.0/24" + } + }, + "action": { + "encapsulate-gre": { + "targets": { + "target": [ + { + "config": { + "destination": "10.10.10.1/32", + "id": "Destination-A" + }, + "id": "Destination-A" + } + ] + } + } + } + } + ] + } + } + ] + } + } + } + ] + } +} +``` + + +## OpenConfig Path and RPC Coverage + +```yaml +paths: + # Config + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/apply-forwarding-policy: + /network-instances/network-instance/policy-forwarding/interfaces/interface/config/interface-id: + /network-instances/network-instance/policy-forwarding/policies/policy/config/policy-id: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/destination-address: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encapsulate-gre/targets/target/config/destination: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/encapsulate-gre/targets/target/config/id: + + # Telemetry + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-pkts: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/matched-octets: + /network-instances/network-instance/afts/policy-forwarding/policy-forwarding-entry/state/counters/packets-forwarded: + /network-instances/network-instance/afts/policy-forwarding/policy-forwarding-entry/state/counters/octets-forwarded: + /network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/state/sequence-id: + + # TODO: Add new OC for GRE encap headers + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/config/index: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/config/next-hop: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/config/index: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/type: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/dst-ip: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/src-ip: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/dscp: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/ip-ttl: + #/network-instances/network-instance/static/next-hop-groups/next-hop-group/nexthops/nexthop/encap-headers/encap-header/gre/config/index: + + # TODO: Add new OC for policy forwarding actions + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/next-hop-group: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/ipv4/config/packet-type: + #/network-instances/network-instance/policy-forwarding/policies/policy/rules/rule/action/config/count: + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + diff --git a/feature/ttl/otg_tests/ingress/metadata.textproto b/feature/ttl/otg_tests/ingress/metadata.textproto new file mode 100644 index 00000000000..5fab6c79c87 --- /dev/null +++ b/feature/ttl/otg_tests/ingress/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "3f1d5c2a-6385-4d32-be4e-0e6fc05a9087" +plan_id: "PF-1.8" +description: "Ingress handling of TTL" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/gribi/otg_tests/mpls_in_udp/README.md b/feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/README.md similarity index 71% rename from feature/gribi/otg_tests/mpls_in_udp/README.md rename to feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/README.md index b430ceead0a..68782b8d6c6 100644 --- a/feature/gribi/otg_tests/mpls_in_udp/README.md +++ b/feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/README.md @@ -1,20 +1,42 @@ -# TE-18.1: gRIBI MPLS in UDP Encapsulation +# TUN-2.9: ECMP hashing on outer and inner packets with MPLSoUDP encapsulation Create AFT entries using gRIBI to match on next hop group in a -network-instance and encapsulate the matching packets in MPLS in UDP with outer header as IPv6 Header. +network-instance and encapsulate the matching packets in MPLS in UDP with +outer header as IPv4 Header or IPv6 header. Do ECMP hashing based on the 5-tuple +for the test. The MPLS in UDP encapsulation is expected to follow [rfc7510](https://datatracker.ietf.org/doc/html/rfc7510#section-3), -but relaxing the requirement for a well-known destination UDP port. gRIBI is +but relaxing the requirement for a well-known destination UDP port. gRIBI is expected to be able to set the destination UDP port. -## Topology +## Testbed Type -* [`featureprofiles/topologies/atedut_2.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_2.testbed) +* [`featureprofiles/topologies/atedut_4.testbed`](https://github.com/openconfig/featureprofiles/blob/main/topologies/atedut_4.testbed) -## Test setup +## Topology: + +```mermaid +graph LR; +A[ATE PORT1] <--IPv4-IPv6--> B[DUT:PORT1]; +C[ATE:PORT2] <--IPv4-IPv6--> D[DUT:PORT2]; +E[ATE:PORT3] <--IPv4-IPv6--> F[DUT:PORT3]; +G[ATE:PORT4] <--IPv4-IPv6--> H[DUT:PORT4]; +``` + +## Procedure + +### Initial setup -TODO: Complete test environment setup steps +* Connect ATE port-1 to DUT port-1, ATE port-2 to DUT port-2, ATE port-3 to + DUT port-3, ATE port-4 to DUT port-4 +* Assign the ipv4 and ipv6 address to the ports as below + DUT-port1 [40.1.1.1/24]<---> ATE-port1 [40.1.1.2/24] + DUT-port2 [41.1.1.1/24]<---> ATE-port2 [41.1.1.2/24] + DUT-port3 [42.1.1.1/24]<---> ATE-port3 [42.1.1.2/24] + DUT-port4 [43.1.1.1/24]<---> ATE-port4 [43.1.1.2/24] + +## Test setup inner_ipv6_dst_A = "2001:aa:bb::1/128" inner_ipv6_dst_B = "2001:aa:bb::2/128" @@ -34,7 +56,7 @@ outer_ip-ttl = "64" ## Procedure -### TE-18.1.1 Match and Encapsulate using gRIBI aft modify +### TUn 2.9 Match and Encapsulate using gRIBI aft modify #### gRIBI RPC content @@ -72,42 +94,23 @@ NH#101 -> { next_hop_group_id: "nhg_A" # new OC path /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/ network_instance: "DEFAULT" } - -# -# entries used for network-instance "NI_B" -IPv6Entry {2001:DB8:2::2/128 (NI_B)} -> NHG#200 (DEFAULT VRF) -IPv4Entry {203.0.113.2/32 (NI_B)} -> NHG#200 (DEFAULT VRF) -> { - {NH#201, DEFAULT VRF} -} - -NH#201 -> { - encap_headers { - encap_header { - index: 1 - mpls { - pushed_mpls_label_stack: [201,] - } - } - encap_header { - index: 2 - udp_v6 { - src_ip: "outer_ipv6_src" - dst_ip: "outer_ipv6_dst_B" - dst_udp_port: "outer_dst_udp_port" - ip_ttl: "outer_ip-ttl" - dscp: "outer_dscp" - } - } - } - next_hop_group_id: "nhg_B" - # network_instance: "DEFAULT" TODO: requires new OC path /network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/network-instance -} ``` -* Send traffic from ATE port 1 to DUT port 1 -* Using OTG, validate ATE port 2 receives MPLS-IN-UDP packets +* Create 5-tuple traffic profile from ATE port 1 to DUT port 1, DUT port 2, + DUT port 3 with the following parameters created. + ipv6 source -> "outer_ipv6_src" + ipv6 destination -> "outer_ipv6_dst_A" + udp src port -> range (10000-30000) + udp dst port -> outer_dst_udp_port + total no of flows -> 100000 +* Configure ECMP hashing configuration on the following parameters + src IP, dst IP, src UDP port, dst UDP port +* Using OTG, validate ATE port 2-4 receives the traffic with MPLS-IN-UDP encap * Validate destination IPs are outer_ipv6_dst_A and outer_ipv6_dst_B * Validate MPLS label is set + * Validate the ecmp hashing is working fine and load balance is happening + across all the 3 ports with tolerance of 1%. + ## OpenConfig Path and RPC Coverage @@ -151,3 +154,4 @@ rpcs: ## Required DUT platform * FFF + diff --git a/feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/metadata.textproto b/feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/metadata.textproto new file mode 100644 index 00000000000..3174abddbf6 --- /dev/null +++ b/feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "f7622128-5fb5-4a9a-95b4-d66a6e59934e" +plan_id: "TUN-2.9" +description: "ECMP hashing on outer and inner packets with MPLSoUDP encapsulation" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto b/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto index 0e10ec8d554..4ec691761d3 100644 --- a/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto +++ b/feature/tunnel/otg_tests/tunnel_acl_based_test/metadata.textproto @@ -6,3 +6,23 @@ plan_id: "TUN-1.9" description: "GRE inner packet DSCP" testbed: TESTBED_DUT_ATE_2LINKS tags: TAGS_AGGREGATION + +platform_exceptions: { + platform: { + vendor: ARISTA + } + deviations: { + interface_enabled: true + default_network_instance: "default" + static_protocol_name: "STATIC" + } +} + +platform_exceptions: { + platform: { + vendor: CISCO + } + deviations: { + ipv4_missing_enabled: true + } +} diff --git a/feature/tunnel/otg_tests/tunnel_interface_based_ipv4_gre_encapsulation_test/tunnel_interface_based_ipv4_gre_encapsulation_test.go b/feature/tunnel/otg_tests/tunnel_interface_based_ipv4_gre_encapsulation_test/tunnel_interface_based_ipv4_gre_encapsulation_test.go index 72aff8db964..e80edd45c90 100644 --- a/feature/tunnel/otg_tests/tunnel_interface_based_ipv4_gre_encapsulation_test/tunnel_interface_based_ipv4_gre_encapsulation_test.go +++ b/feature/tunnel/otg_tests/tunnel_interface_based_ipv4_gre_encapsulation_test/tunnel_interface_based_ipv4_gre_encapsulation_test.go @@ -154,7 +154,11 @@ func TestTunnelEncapsulationByGREOverIPv4WithLoadBalance(t *testing.T) { time.Sleep(30 * time.Second) t.Logf("Start Traffic flow configuraturation in OTG") configureTrafficFlowsToEncasulation(t, top, ateport1, ateport2, ateport3, &otgIntf1, dutIntf1.MAC) - t.Logf(top.Marshal().ToJson()) + if json, err := top.Marshal().ToJson(); err != nil { + t.Errorf("trouble converting %v to json: %v", top, err) + } else { + t.Logf("%s", json) + } ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) time.Sleep(30 * time.Second) diff --git a/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/tunnel_interface_based_ipv6_gre_encapsulation_test.go b/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/tunnel_interface_based_ipv6_gre_encapsulation_test.go index ff60dfc1989..aa31d125527 100644 --- a/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/tunnel_interface_based_ipv6_gre_encapsulation_test.go +++ b/feature/tunnel/otg_tests/tunnel_interface_based_ipv6_gre_encapsulation_test/tunnel_interface_based_ipv6_gre_encapsulation_test.go @@ -160,7 +160,11 @@ func TestTunnelEncapsulationByGREOverIPv6WithLoadBalance(t *testing.T) { time.Sleep(30 * time.Second) t.Logf("Start Traffic flow configuraturation in OTG") configureTrafficFlowsToEncasulation(t, top, ateport1, ateport2, ateport3, &otgIntf1, dutIntf1.MAC) - t.Log(top.Marshal().ToJson()) + if json, err := top.Marshal().ToJson(); err != nil { + t.Errorf("trouble converting %v to json: %v", top, err) + } else { + t.Logf("%s", json) + } ate.OTG().PushConfig(t, top) ate.OTG().StartProtocols(t) time.Sleep(30 * time.Second) diff --git a/feature/tunnel/otg_tests/tunnel_interface_based_resize_test/metadata.textproto b/feature/tunnel/otg_tests/tunnel_interface_based_resize_test/metadata.textproto new file mode 100644 index 00000000000..73bf5a701e1 --- /dev/null +++ b/feature/tunnel/otg_tests/tunnel_interface_based_resize_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "bf4120d7-6de4-4aa5-9071-3f14948a8847" +plan_id: "TUN-1.6" +description: "Tunnel End Point Resize for Ecapsulation - Interface Based GRE Tunnel" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/feature/urpf/otg_tests/urpf_vrf_test/README.md b/feature/urpf/otg_tests/urpf_vrf_test/README.md new file mode 100644 index 00000000000..0e3ad50f6b8 --- /dev/null +++ b/feature/urpf/otg_tests/urpf_vrf_test/README.md @@ -0,0 +1,177 @@ +# URPF-1.1: uRPF validation from non-default network-instance +## Summary +This test verifies that uRPF validation occurs in a non-default network-instance while the forwarding lookup takes place in the default network-instance. + +## Topology +- Create the following connections: + +```mermaid +graph LR; + A[ATE:Port1] <--eBGP--> B[Port1:DUT:Port2];B <--iBGP--> C[Port2:ATE]; +``` + +## Configuration generation of DUT and ATE +### Baseline DUT configuration +- Configure EBGP[ASN200:ASN100] between ATE port1 – DUT port1 +- Configure IBGP[ASN100] between ATE port 2 – DUT +- Configure a non-default vrf to host routes learned from eBGP neighborship(ATE port1) and to constraint the uRPF lookup in the non-default vrf +- Routes in non-default VRF: + - Configure the static routes for `IPv4Prefix1/24` `IPv6Prefix1/64` in non-default VRF +- DUT has DUT:Port1 and DUT:Port2 in the default network-instance +- DUT's IP sub-interfaces belong to Default VRF +- DUT port1 has uRPF policy at the ingress DUT:PORT1 +### Baseline ATE configuration +- Configure EBGP[ASN200] on ATE:Port1 +#### ATE Route Advertisements: +- ATE:Port1 advertises following valid prefixes over EBGP to DUT:Port1 + - IPv4Prefix1/24 IPv6Prefix1/64 community 200:200 +- ATE:Port1 advertises following invalid prefixes over EBGP to DUT:Port1 + - IPv4prefix2/24 IPv6prefix2/64 +- ATE:Port2 advertises following prefixes over IBGP to DUT:Port2 + - IPv4prefix3/24 IPv6prefix3/64 +## Procedure: +### URPF-1.1.1 - uRPF with valid source IP address +- Flow type: Native IPv4 or IPv6 traffic +- Simulate the below stated flows from ATE:Port1 to ATE:Port2: + - IPv4Prefix1/24 to IPv4prefix3/24 at a rate of 100 packets/sec + - IPv6Prefix1/24 to IPv6prefix3/24 at a rate of 100 packets/sec +- Success Criteria: + - All traffic should reach port 2 and there should be no packet loss + - The packets sent by the sender tester is equal to the packets on the receiving tester port and also should be equal to the sum of packets seen by default. +### URPF-1.1.2 - uRPF with invalid source IP address +- Flow type: Native IPv4 or IPv6 traffic +- The invalid prefixes should not be installed in non-default vrf +- Simulate the below stated flows from ATE:Port1 to ATE:Port2: + - IPv4prefix2/64 to IPv4prefix3/64 at a rate of 100 packets/sec + - IPv6prefix2/64 to IPv6prefix3/64 at a rate of 100 packets/sec +- Success Criteria: + - All traffic should be dropped by DUT since the non-default vrf couldn't validate the SIP + - Validation shouldn't happen against the default route present in non-default vrf + - The uRPF drop packet counter should increment and should be equal to the packets sent by the sender tester + - The packets sent by the sender tester are not equal to the packets on the receiving tester port and also the sum of packets seen by the Port2 should be zero packets. +### URPF-1.1.3 - uRPF with valid source IP address and GUE encapsulation +- Flow type: Native IPv4 or IPv6 traffic encapsulated by GUE variant 1 on DUT +- The uRPF check should happen before the encapsulation is performed by the DUT +- Use canonical OC from [RT-3.53](https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/encapsulation/otg_tests/static_encap_gue_ipv4/README.md) for enabling GUE encapsulation on DUT + - Configure the DUT to provision the outer IP header source and destination address as below: + - Source address is DUT Loopback address + - Destination address is ATE:PORT2 IPv4 address + - Final encapsulated Packet format: + - ```[[src IP: DUT Loopback | Dst IP: ATE:Port2][udp src port: udp1 | udp dst port: udp2 or udp3]]over[payload]``` +- Simulate the below stated flows from ATE:Port1 to ATE:Port2: + - IPv4Prefix1/24 to IPv4prefix3/24 at a rate of 100 packets/sec + - IPv6Prefix1/64 to IPv6prefix3/64 at a rate of 100 packets/sec +- Success Criteria: + - All traffic should reach port 2 and there should be no packet loss. + - The packets sent by the sender tester are equal to the packets on the receiving tester port and also the sum of packets seen by the Port2 should be equal to the sum of the packets sent by the sender tester port. +### URPF-1.1.4 - uRPF with invalid source IP address and GUE encapsulation +- Flow type: Native IPv4 or IPv6 traffic encapsulated by GUE variant 1 on DUT +- The uRPF check should happen before the encapsulation is performed by the DUT +- Use canonical OC from [RT-3.53](https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/encapsulation/otg_tests/static_encap_gue_ipv4/README.md) for enabling GUE encapsulation on DUT + - Configure the DUT to provision the outer IP header source and destination address as below: + - Source address is DUT Loopback address + - Destination address is ATE:PORT2 IPv4 address + - Final encapsulated Packet format: + - ```[[src IP: DUT Loopback| Dst IP: ATE:Port2][udp src port: udp1 | udp dst port: udp2 or udp3]]over[payload]``` +- Simulate the below stated flows from ATE:Port1 to ATE:Port2: + - IPv4prefix2/24 to IPv4prefix3/24 at a rate of 100 packets/sec + - IPv6prefix2/64 to IPv6prefix3/64 at a rate of 100 packets/sec +- Success Criteria: + - All traffic should be dropped by DUT since the non-default vrf couldn't validate the source IP address of the flow + - Validation shouldn't happen against the default route present in non-default vrf + - The uRPF drop packet counter should increment and should be equal to the packets sent by the sender tester + - The packets sent by the sender tester are not equal to the packets on the receiving tester port and also the sum of packets seen by the Port2 should be zero packets. + +## Canonical OpenConfig for URPF through another instance +TODO: URPF via instance OC path are being proposed by to be updated by [#1320](https://github.com/openconfig/public/pull/1320) + +```json +{ + "openconfig-interfaces": { + "interfaces": { + "interface": [ + { + "name": "example-interface-name", + "subinterfaces": { + "subinterface": [ + { + "index": 0, + "ipv4": { + "urpf": { + "config": { + "enabled": true, + "mode": "LOOSE", + "allow-default-route": false, + "allow-drop-next-hop": false, + "allow-feasible-path": false, + "urpf-lookup-network-instance": "URPF-Instance" + } + } + }, + "ipv6": { + "urpf": { + "config": { + "enabled": true, + "mode": "LOOSE", + "allow-default-route": false, + "allow-drop-next-hop": false, + "allow-feasible-path": false, + "urpf-lookup-network-instance": "URPF-Instance" + } + } + } + } + ] + } + } + ] + } + } +} +``` + +## OpenConfig Path and RPC Coverage +TODO: URPF via instance OC path are being proposed by to be updated by [#1320](https://github.com/openconfig/public/pull/1320) + +```yaml +paths: + ## Config Parameter Coverage + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/config/mode: + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/config/allow-default-route: + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/config/allow-drop-next-hop: + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/config/allow-feasible-path: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/config/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/config/mode: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/config/allow-default-route: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/config/allow-drop-next-hop: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/config/allow-feasible-path: + # /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/config/urpf-lookup-network-instance: + # /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/config/urpf-lookup-network-instance: + + ## Telemetry Parameter Coverage + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/state/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/state/mode: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/urpf-drop-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv4/state/counters/urpf-drop-bytes: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/state/enabled: + /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/state/mode: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/urpf-drop-pkts: + /interfaces/interface/subinterfaces/subinterface/ipv6/state/counters/urpf-drop-bytes: + # /interfaces/interface/subinterfaces/subinterface/ipv4/urpf/state/urpf-lookup-network-instance: + # /interfaces/interface/subinterfaces/subinterface/ipv6/urpf/state/urpf-lookup-network-instance: + + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * FFF - fixed form factor diff --git a/feature/urpf/otg_tests/urpf_vrf_test/metadata.textproto b/feature/urpf/otg_tests/urpf_vrf_test/metadata.textproto new file mode 100644 index 00000000000..c11ba430683 --- /dev/null +++ b/feature/urpf/otg_tests/urpf_vrf_test/metadata.textproto @@ -0,0 +1,7 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/metadata.proto +# proto-message: Metadata + +uuid: "2fe85525-35f5-41f5-b1e5-5ad0986a4f8d" +plan_id: "URPF-1.1" +description: "uRPF validation from non-default network-instance" +testbed: TESTBED_DUT_ATE_2LINKS diff --git a/go.mod b/go.mod index 0ea5ebfde58..e1e2e049920 100644 --- a/go.mod +++ b/go.mod @@ -1,77 +1,84 @@ module github.com/openconfig/featureprofiles -go 1.23.4 +go 1.24.7 require ( - cloud.google.com/go/pubsub v1.36.1 - cloud.google.com/go/storage v1.38.0 + cloud.google.com/go/pubsub v1.45.1 + cloud.google.com/go/storage v1.49.0 github.com/cisco-open/go-p4 v0.1.2 github.com/go-git/go-billy/v5 v5.6.0 github.com/go-git/go-git/v5 v5.13.0 - github.com/golang/glog v1.2.4 - github.com/google/go-cmp v0.6.0 + github.com/golang/glog v1.2.5 + github.com/google/go-cmp v0.7.0 github.com/google/go-github/v50 v50.1.0 github.com/google/gopacket v1.1.19 github.com/google/uuid v1.6.0 github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 github.com/kr/pretty v0.3.1 - github.com/open-traffic-generator/snappi/gosnappi v1.30.1 + github.com/open-traffic-generator/snappi/gosnappi v1.33.4 github.com/openconfig/containerz v0.0.0-20250408205203-029560584812 github.com/openconfig/entity-naming v0.0.0-20250108173956-784f643e8b64 github.com/openconfig/gnmi v0.14.1 - github.com/openconfig/gnoi v0.6.1-0.20250206212518-26d177339690 - github.com/openconfig/gnoigo v0.0.0-20250408191134-9985281d2e03 - github.com/openconfig/gnsi v1.8.0 - github.com/openconfig/gocloser v0.0.0-20220310182203-c6c950ed3b0b - github.com/openconfig/goyang v1.6.0 + github.com/openconfig/gnoi v0.6.2 + github.com/openconfig/gnoigo v0.0.0-20250422174708-0b77c31c7d66 + github.com/openconfig/gnsi v1.9.0 + github.com/openconfig/gocloser v0.0.0-20250211195114-79e08bd41eef + github.com/openconfig/goyang v1.6.3 github.com/openconfig/gribi v1.9.0 - github.com/openconfig/gribigo v0.0.0-20240829231637-69cf06726cc3 + github.com/openconfig/gribigo v0.1.1 github.com/openconfig/kne v0.1.18 github.com/openconfig/models-ci v1.0.2-0.20231113233730-f0986391428e - github.com/openconfig/ondatra v0.9.1 + github.com/openconfig/ondatra v0.12.2 github.com/openconfig/replayer v0.0.0-20240110192655-4e9cf83d8d30 github.com/openconfig/testt v0.0.0-20220311054427-efbb1a32ec07 github.com/openconfig/ygnmi v0.12.0 - github.com/openconfig/ygot v0.29.20 + github.com/openconfig/ygot v0.33.0 github.com/p4lang/p4runtime v1.4.1 github.com/pborman/uuid v1.2.1 github.com/protocolbuffers/txtpbfmt v0.0.0-20240823084532-8e6b51fa9bef - github.com/spf13/cobra v1.8.1 - github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c - github.com/spf13/viper v1.19.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/pflag v1.0.7 + github.com/spf13/viper v1.20.1 github.com/yoheimuta/go-protoparser/v4 v4.9.0 github.com/yuin/goldmark v1.4.13 - golang.org/x/crypto v0.36.0 - golang.org/x/exp v0.0.0-20241210194714-1829a127f884 - golang.org/x/text v0.23.0 - google.golang.org/api v0.171.0 - google.golang.org/grpc v1.72.0 - google.golang.org/protobuf v1.36.6 + golang.org/x/crypto v0.41.0 + golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b + golang.org/x/text v0.28.0 + google.golang.org/api v0.215.0 + google.golang.org/grpc v1.75.0 + google.golang.org/protobuf v1.36.8 gopkg.in/yaml.v2 v2.4.0 gopkg.in/yaml.v3 v3.0.1 k8s.io/klog/v2 v2.130.1 ) require ( - github.com/Masterminds/semver/v3 v3.3.1 // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect github.com/Microsoft/go-winio v0.6.2 // indirect - golang.org/x/oauth2 v0.26.0 + golang.org/x/oauth2 v0.30.0 ) require ( bitbucket.org/creachadair/stringset v0.0.14 // indirect - cel.dev/expr v0.20.0 // indirect - cloud.google.com/go v0.112.1 // indirect - cloud.google.com/go/compute/metadata v0.6.0 // indirect - cloud.google.com/go/iam v1.1.6 // indirect + cel.dev/expr v0.24.0 // indirect + cloud.google.com/go v0.116.0 // indirect + cloud.google.com/go/auth v0.13.0 // indirect + cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect + cloud.google.com/go/compute/metadata v0.7.0 // indirect + cloud.google.com/go/iam v1.2.2 // indirect + cloud.google.com/go/monitoring v1.21.2 // indirect dario.cat/mergo v1.0.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect + github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect github.com/ProtonMail/go-crypto v1.1.3 // indirect github.com/aristanetworks/arista-ceoslab-operator/v2 v2.1.2 // indirect github.com/carlmontanari/difflibgo v0.0.0-20210718194309-31b9e131c298 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/cloudflare/circl v1.3.7 // indirect - github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect + github.com/cloudflare/circl v1.6.1 // indirect + github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect github.com/creack/pty v1.1.18 // indirect github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect @@ -82,25 +89,26 @@ require ( github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect github.com/evanphx/json-patch/v5 v5.6.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/ghodss/yaml v1.0.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect - github.com/go-jose/go-jose/v4 v4.0.4 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/go-jose/go-jose/v4 v4.1.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic v0.6.9 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/gofuzz v1.2.0 // indirect - github.com/google/s2a-go v0.1.7 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect - github.com/googleapis/gax-go/v2 v2.12.3 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect + github.com/google/s2a-go v0.1.8 // indirect + github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect + github.com/googleapis/gax-go/v2 v2.14.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect github.com/imdario/mergo v0.3.15 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect @@ -110,37 +118,34 @@ require ( github.com/kevinburke/ssh_config v1.2.0 // indirect github.com/kr/text v0.2.0 // indirect github.com/kylelemons/godebug v1.1.0 // indirect - github.com/magiconair/properties v1.8.9 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/moby/spdystream v0.2.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/networkop/meshnet-cni v0.3.1-0.20230525201116-d7c306c635cf // indirect github.com/open-traffic-generator/keng-operator v0.3.28 // indirect - github.com/openconfig/attestz v0.2.0 // indirect - github.com/openconfig/bootz v0.3.1 // indirect + github.com/openconfig/attestz v0.5.0 // indirect + github.com/openconfig/bootz v0.6.0 // indirect github.com/openconfig/gnpsi v0.3.2 // indirect github.com/openconfig/grpctunnel v0.1.0 // indirect github.com/openconfig/lemming/operator v0.2.0 // indirect github.com/patrickmn/go-cache v2.1.0+incompatible // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/pjbgf/sha1cd v0.3.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect github.com/rogpeppe/go-internal v1.13.1 // indirect - github.com/sagikazarmark/locafero v0.6.0 // indirect - github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sagikazarmark/locafero v0.10.0 // indirect github.com/scrapli/scrapligo v1.1.11 // indirect github.com/scrapli/scrapligocfg v1.0.0 // indirect github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect github.com/sirikothe/gotextfsm v1.0.1-0.20200816110946-6aa2cfd355e4 // indirect github.com/skeema/knownhosts v1.3.0 // indirect - github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.11.0 // indirect - github.com/spf13/cast v1.7.0 // indirect + github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.9.2 // indirect github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect github.com/srl-labs/srl-controller v0.6.1 // indirect github.com/srl-labs/srlinux-scrapli v0.6.0 // indirect @@ -149,23 +154,29 @@ require ( github.com/zeebo/errs v1.4.0 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/auto/sdk v1.1.0 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 // indirect - go.opentelemetry.io/otel v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.34.0 // indirect - go.opentelemetry.io/otel/trace v1.34.0 // indirect + go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect + go.opentelemetry.io/otel v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 // indirect + go.opentelemetry.io/otel/metric v1.37.0 // indirect + go.opentelemetry.io/otel/sdk v1.37.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect + go.opentelemetry.io/otel/trace v1.37.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sync v0.12.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect + golang.org/x/net v0.43.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/term v0.34.0 // indirect golang.org/x/time v0.9.0 // indirect - google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect + google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c // indirect gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect k8s.io/api v0.26.3 // indirect k8s.io/apimachinery v0.26.3 // indirect diff --git a/go.sum b/go.sum index 1c8eb8d0d12..cd203d5ddad 100644 --- a/go.sum +++ b/go.sum @@ -1,26 +1,46 @@ bitbucket.org/creachadair/stringset v0.0.14 h1:t1ejQyf8utS4GZV/4fM+1gvYucggZkfhb+tMobDxYOE= bitbucket.org/creachadair/stringset v0.0.14/go.mod h1:Ej8fsr6rQvmeMDf6CCWMWGb14H9mz8kmDgPPTdiVT0w= -cel.dev/expr v0.20.0 h1:OunBvVCfvpWlt4dN7zg3FM6TDkzOePe1+foGJ9AXeeI= -cel.dev/expr v0.20.0/go.mod h1:MrpN08Q+lEBs+bGYdLxxHkZoUSsCp0nSKTs0nTymJgw= +cel.dev/expr v0.24.0 h1:56OvJKSH3hDGL0ml5uSxZmz3/3Pq4tJ+fb1unVLAFcY= +cel.dev/expr v0.24.0/go.mod h1:hLPLo1W4QUmuYdA72RBX06QTs6MXw941piREPl3Yfiw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.112.1 h1:uJSeirPke5UNZHIb4SxfZklVSiWWVqW4oXlETwZziwM= -cloud.google.com/go v0.112.1/go.mod h1:+Vbu+Y1UU+I1rjmzeMOb/8RfkKJK2Gyxi1X6jJCZLo4= -cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= -cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= -cloud.google.com/go/iam v1.1.6 h1:bEa06k05IO4f4uJonbB5iAgKTPpABy1ayxaIZV/GHVc= -cloud.google.com/go/iam v1.1.6/go.mod h1:O0zxdPeGBoFdWW3HWmBxJsk0pfvNM/p/qa82rWOGTwI= -cloud.google.com/go/kms v1.15.7 h1:7caV9K3yIxvlQPAcaFffhlT7d1qpxjB1wHBtjWa13SM= -cloud.google.com/go/kms v1.15.7/go.mod h1:ub54lbsa6tDkUwnu4W7Yt1aAIFLnspgh0kPGToDukeI= -cloud.google.com/go/pubsub v1.36.1 h1:dfEPuGCHGbWUhaMCTHUFjfroILEkx55iUmKBZTP5f+Y= -cloud.google.com/go/pubsub v1.36.1/go.mod h1:iYjCa9EzWOoBiTdd4ps7QoMtMln5NwaZQpK1hbRfBDE= -cloud.google.com/go/storage v1.38.0 h1:Az68ZRGlnNTpIBbLjSMIV2BDcwwXYlRlQzis0llkpJg= -cloud.google.com/go/storage v1.38.0/go.mod h1:tlUADB0mAb9BgYls9lq+8MGkfzOXuLrnHXlpHmvFJoY= +cloud.google.com/go v0.116.0 h1:B3fRrSDkLRt5qSHWe40ERJvhvnQwdZiHu0bJOpldweE= +cloud.google.com/go v0.116.0/go.mod h1:cEPSRWPzZEswwdr9BxE6ChEn01dWlTaF05LiC2Xs70U= +cloud.google.com/go/auth v0.13.0 h1:8Fu8TZy167JkW8Tj3q7dIkr2v4cndv41ouecJx0PAHs= +cloud.google.com/go/auth v0.13.0/go.mod h1:COOjD9gwfKNKz+IIduatIhYJQIc0mG3H102r/EMxX6Q= +cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyTuciBG5hFkU= +cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= +cloud.google.com/go/compute/metadata v0.7.0 h1:PBWF+iiAerVNe8UCHxdOt6eHLVc3ydFeOCw78U8ytSU= +cloud.google.com/go/compute/metadata v0.7.0/go.mod h1:j5MvL9PprKL39t166CoB1uVHfQMs4tFQZZcKwksXUjo= +cloud.google.com/go/iam v1.2.2 h1:ozUSofHUGf/F4tCNy/mu9tHLTaxZFLOUiKzjcgWHGIA= +cloud.google.com/go/iam v1.2.2/go.mod h1:0Ys8ccaZHdI1dEUilwzqng/6ps2YB6vRsjIe00/+6JY= +cloud.google.com/go/kms v1.20.1 h1:og29Wv59uf2FVaZlesaiDAqHFzHaoUyHI3HYp9VUHVg= +cloud.google.com/go/kms v1.20.1/go.mod h1:LywpNiVCvzYNJWS9JUcGJSVTNSwPwi0vBAotzDqn2nc= +cloud.google.com/go/logging v1.12.0 h1:ex1igYcGFd4S/RZWOCU51StlIEuey5bjqwH9ZYjHibk= +cloud.google.com/go/logging v1.12.0/go.mod h1:wwYBt5HlYP1InnrtYI0wtwttpVU1rifnMT7RejksUAM= +cloud.google.com/go/longrunning v0.6.2 h1:xjDfh1pQcWPEvnfjZmwjKQEcHnpz6lHjfy7Fo0MK+hc= +cloud.google.com/go/longrunning v0.6.2/go.mod h1:k/vIs83RN4bE3YCswdXC5PFfWVILjm3hpEUlSko4PiI= +cloud.google.com/go/monitoring v1.21.2 h1:FChwVtClH19E7pJ+e0xUhJPGksctZNVOk2UhMmblmdU= +cloud.google.com/go/monitoring v1.21.2/go.mod h1:hS3pXvaG8KgWTSz+dAdyzPrGUYmi2Q+WFX8g2hqVEZU= +cloud.google.com/go/pubsub v1.45.1 h1:ZC/UzYcrmK12THWn1P72z+Pnp2vu/zCZRXyhAfP1hJY= +cloud.google.com/go/pubsub v1.45.1/go.mod h1:3bn7fTmzZFwaUjllitv1WlsNMkqBgGUb3UdMhI54eCc= +cloud.google.com/go/storage v1.49.0 h1:zenOPBOWHCnojRd9aJZAyQXBYqkJkdQS42dxL55CIMw= +cloud.google.com/go/storage v1.49.0/go.mod h1:k1eHhhpLvrPjVGfo0mOUPEJ4Y2+a/Hv5PiwehZI9qGU= +cloud.google.com/go/trace v1.11.2 h1:4ZmaBdL8Ng/ajrgKqY5jfvzqMXbrDcBsUGXOT9aqTtI= +cloud.google.com/go/trace v1.11.2/go.mod h1:bn7OwXd4pd5rFuAnTrzBuoZ4ax2XQeG3qNgYmfCy0Io= dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= -github.com/Masterminds/semver/v3 v3.3.1/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 h1:UQUsRi8WTzhZntp5313l+CHIAT95ojUI2lpP/ExlZa4= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0/go.mod h1:Cz6ft6Dkn3Et6l2v2a9/RpN7epQ1GtDlO6lj8bEcOvw= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 h1:UQ0AhxogsIRZDkElkblfnwjc3IaltCm2HUMvezQaL7s= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1/go.mod h1:jyqM3eLpJ3IbIFDTKVz2rF9T/xWGW0rIriGwnz8l9Tk= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1 h1:oTX4vsorBZo/Zdum6OKPA4o7544hm6smoRv1QjpTwGo= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/cloudmock v0.48.1/go.mod h1:0wEl7vrAD8mehJyohS9HZy+WyEOaQO2mJx86Cvh93kM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 h1:8nn+rsCvTq9axyEh382S0PFLBeaFwNsT43IrPWzctRU= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1/go.mod h1:viRWSEhtMZqz1rhwmOVKkWl6SwmVowfL9O2YR5gI2PE= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= @@ -40,9 +60,10 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/carlmontanari/difflibgo v0.0.0-20210718194309-31b9e131c298 h1:Y8rTum6LZ8oP/2aC+OaaP76OCjHbunKMkim81mzNCH0= github.com/carlmontanari/difflibgo v0.0.0-20210718194309-31b9e131c298/go.mod h1:+3MuSIeC3qmdSesR12cTLeb47R/Vvo+bHdB6hC5HShk= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= @@ -50,14 +71,14 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/cisco-open/go-p4 v0.1.2 h1:ycRluNWG3yz86pnEg78d87C25rx7fJQvEv5l77TKfQ0= github.com/cisco-open/go-p4 v0.1.2/go.mod h1:pXywOqfJvcYm7PB2qj8Ib3w9EvAAL44xLbGaUKpeX4Y= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= -github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= +github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 h1:Om6kYQYDUk5wWbT0t0q6pvyM49i9XZAv9dDrkDA7gjk= -github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= -github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 h1:aQ3y1lwWyqYPiWZThqv1aFbZMiM9vblcSArJRf2Irls= +github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443/go.mod h1:W+zGtBO5Y1IgJhy4+A9GOqVhqLpfZi+vwmdNXUehLA8= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= @@ -105,8 +126,8 @@ github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSw github.com/flowstack/go-jsonschema v0.1.1/go.mod h1:yL7fNggx1o8rm9RlgXv7hTBWxdBM0rVwpMwimd3F3N0= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= @@ -119,13 +140,13 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII= github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= -github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E= -github.com/go-jose/go-jose/v4 v4.0.4/go.mod h1:NKb5HO1EZccyMpiZNbdUw/14tiXNyUJh188dfnMCAfc= +github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= +github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.2.3 h1:a9vnzlIBPQBBkeaR9IuMUfmVOrQlkoC4YfPoFkX3T7A= @@ -139,11 +160,13 @@ github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/glog v1.2.4 h1:CNNw5U8lSiiBk7druxtSHHTsRWcxKoac6kZKm2peBBc= -github.com/golang/glog v1.2.4/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= @@ -173,11 +196,10 @@ github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/go-github/v50 v50.1.0 h1:hMUpkZjklC5GJ+c3GquSqOP/T4BNsB7XohaPhtMOzRk= github.com/google/go-github/v50 v50.1.0/go.mod h1:Ev4Tre8QoKiolvbpOSG3FIi4Mlon3S2Nt9W5JYqKiwA= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -187,30 +209,30 @@ github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8= github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= +github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= +github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/s2a-go v0.1.7 h1:60BLSyTrOV4/haCDW4zb1guZItoSq8foHCXrAnjBo/o= -github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8F0Qdw= +github.com/google/s2a-go v0.1.8 h1:zZDs9gcbt9ZPLV0ndSyQk6Kacx2g/X+SKYovpnz3SMM= +github.com/google/s2a-go v0.1.8/go.mod h1:6iNWHTpQ+nfNRN5E00MSdfDwVesa8hhS32PhPO8deJA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= -github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= -github.com/googleapis/gax-go/v2 v2.12.3 h1:5/zPPDvw8Q1SuXjrqrZslrqT7dL/uJT2CQii/cLCKqA= -github.com/googleapis/gax-go/v2 v2.12.3/go.mod h1:AKloxT6GtNbaLm8QTNSidHUVsHYcBHwWRvkNFJUQcS4= +github.com/googleapis/enterprise-certificate-proxy v0.3.4 h1:XYIDZApgAnrN1c855gTgghdIA6Stxb52D5RnLI1SLyw= +github.com/googleapis/enterprise-certificate-proxy v0.3.4/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= +github.com/googleapis/gax-go/v2 v2.14.1 h1:hb0FFeiPaQskmvakKu5EbCbpntQn48jyHuvrkurSS/Q= +github.com/googleapis/gax-go/v2 v2.14.1/go.mod h1:Hb/NubMaVM88SrNkvl8X/o8XWwDJEPqouaLeN2IUxoA= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= github.com/h-fam/errdiff v1.0.2 h1:rPsW4ob2fMOIulwTEoZXaaUIuud7XUudw5SLKTZj3Ss= github.com/h-fam/errdiff v1.0.2/go.mod h1:FOzgnHXSEE3rRvmGXgmiqWl+H3lwLywYm9CSXqXrSTg= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= @@ -220,8 +242,6 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA= -github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report/v2 v2.1.0 h1:X3+hPYlSczH9IMIpSC9CQSZA0L+BipYafciZUWHEmsc= @@ -247,22 +267,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= -github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/mdlayher/genetlink v1.3.2 h1:KdrNKe+CTu+IbZnm/GVUMXSqBBLqcGpRDa0xkQy56gw= -github.com/mdlayher/genetlink v1.3.2/go.mod h1:tcC3pkCrPUGIKKsCsp0B3AdaaKuHtaxoJRz3cc+528o= -github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g= -github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw= -github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos= -github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= -github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -283,58 +293,54 @@ github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k= github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY= github.com/open-traffic-generator/keng-operator v0.3.28 h1:FpDe1wtGODN7ByAhF2LxMIlbDqb5yVmbSE5Y49nyc28= github.com/open-traffic-generator/keng-operator v0.3.28/go.mod h1:+koaOnSyrJHdzxnaye+M6k+ZbszQlWI9u3tMxSpORNA= -github.com/open-traffic-generator/snappi/gosnappi v1.30.1 h1:vGpQbwx8RSlv/jIR37oxw/Ek8NtvRnJODCG+QScJdFo= -github.com/open-traffic-generator/snappi/gosnappi v1.30.1/go.mod h1:tNBZ7AOMuD1SYrIIEcBaFVxOz/Ney496RJDzGBzFV18= -github.com/openconfig/attestz v0.2.0 h1:VuksFIG1OlGnRuUpdTFAkMyAY59ITvyLbp4AtiTXV64= -github.com/openconfig/attestz v0.2.0/go.mod h1:byY6H68zm3VXmQHEb4O4OZtRtFyHEjkmzrvIljYc79Y= -github.com/openconfig/bootz v0.3.1 h1:Q0mThGmZiX/kht+crar6FtLVxqdjUS/deMnpcNX+F7c= -github.com/openconfig/bootz v0.3.1/go.mod h1:IhVtV9zS/2i8rKXHkRW9eD2UV6zGeIXYtLcEzAeyc6A= +github.com/open-traffic-generator/snappi/gosnappi v1.33.4 h1:H5Vro50arrtLUsXaBI4eQF8sB4MzO3ilsMA0UOJFGP4= +github.com/open-traffic-generator/snappi/gosnappi v1.33.4/go.mod h1:rl0ggGYzmJIQfGBbpg7DopLiWVbopSiJPmfCmw2m1tU= +github.com/openconfig/attestz v0.5.0 h1:1XDcw8CaAxS9+Yrrmj69qChwa2Y5q/TlcU9h4up3Qrc= +github.com/openconfig/attestz v0.5.0/go.mod h1:b4sZBEyj054i4TKG1iB/ZQXMvz/15KDSDmhGVANYD9g= +github.com/openconfig/bootz v0.6.0 h1:QAbYVKKYqoJfO7Vzmv4PTBTcPe1PWRrSz0vgALvnYdk= +github.com/openconfig/bootz v0.6.0/go.mod h1:QStTgg5QBpnjVRYi3xsP9JObziPJGeP/qhbj+EAuXtM= github.com/openconfig/containerz v0.0.0-20250408205203-029560584812 h1:qjDaXTJ8aOKUJmZrUNuYEn7rMARSkQaZUjgGHvmmqI8= github.com/openconfig/containerz v0.0.0-20250408205203-029560584812/go.mod h1:0anbhHTN0LNn42iX5k8RTBiiO2mCp4q3vxcBHCKMOCg= github.com/openconfig/entity-naming v0.0.0-20250108173956-784f643e8b64 h1:pS4NcCl49ker3FYRkvY+erNUw1CgL/lB8gnDQbax6Yk= github.com/openconfig/entity-naming v0.0.0-20250108173956-784f643e8b64/go.mod h1:FDF5sbP9BbP2IM6EUopcAWfGJ5OPZ4VV2EYQ9vvqLjA= -github.com/openconfig/gnmi v0.10.0/go.mod h1:Y9os75GmSkhHw2wX8sMsxfI7qRGAEcDh8NTa5a8vj6E= github.com/openconfig/gnmi v0.14.1 h1:qKMuFvhIRR2/xxCOsStPQ25aKpbMDdWr3kI+nP9bhMs= github.com/openconfig/gnmi v0.14.1/go.mod h1:whr6zVq9PCU8mV1D0K9v7Ajd3+swoN6Yam9n8OH3eT0= -github.com/openconfig/gnoi v0.6.1-0.20250206212518-26d177339690 h1:YX11RRu3kF5jnB9yipAkjlENi5aq2EwHVZ/g7lgdDss= -github.com/openconfig/gnoi v0.6.1-0.20250206212518-26d177339690/go.mod h1:oaXjN+j2dKD4S1yk/gb7XrHham+Cp6tRVdQm9pp+Hgw= -github.com/openconfig/gnoigo v0.0.0-20250408191134-9985281d2e03 h1:MC0S99f68ob/FuypMCobxSkeT5YGpbpwLg4Wr/XcCF0= -github.com/openconfig/gnoigo v0.0.0-20250408191134-9985281d2e03/go.mod h1:pCijvudnCsk0fuj0JV1cWprfFQsrOD9VwFuF6gWZQsI= +github.com/openconfig/gnoi v0.6.2 h1:o828QHknW5WIbQYSqXkJK2ZnSM0TF8roQ2nzpH/CdBE= +github.com/openconfig/gnoi v0.6.2/go.mod h1:5h/f7PwVl4Ux22UlS9H1/1lmQzmF7+XHpAlVUGEmc5A= +github.com/openconfig/gnoigo v0.0.0-20250422174708-0b77c31c7d66 h1:7gLDLy4IZA5hdLUiiRKlh0A0lzg3ALVuuKj8/0UJav4= +github.com/openconfig/gnoigo v0.0.0-20250422174708-0b77c31c7d66/go.mod h1:3gz4iQ3Bg8qicUwqQ7JV7d+FpMIzdUIlHWY2Wiee/EE= github.com/openconfig/gnpsi v0.3.2 h1:+bl1bXMOTrWOcGydWB+8wGgvxlgvL8Y6joAiWFU5sog= github.com/openconfig/gnpsi v0.3.2/go.mod h1:+Qj2PwadJ/jvGkH6H/A3XO9ZRKQRVtl3A30ubwz0M18= -github.com/openconfig/gnsi v1.8.0 h1:IcS27GiTS/ZMUnNdsV91NnTs5BUFVruF8leaxX/kbSM= -github.com/openconfig/gnsi v1.8.0/go.mod h1:0t1k7tyoU+fv9c1tzx3PZ4cx4Z2uh6TWuEvUpDyIvD0= -github.com/openconfig/gocloser v0.0.0-20220310182203-c6c950ed3b0b h1:NSYuxdlOWLldNpid1dThR6Dci96juXioUguMho6aliI= -github.com/openconfig/gocloser v0.0.0-20220310182203-c6c950ed3b0b/go.mod h1:uhC/ybmPapgeyAL2b9ZrUQ+DZE+DB+J+/7377PX+lek= -github.com/openconfig/goyang v0.0.0-20200115183954-d0a48929f0ea/go.mod h1:dhXaV0JgHJzdrHi2l+w0fZrwArtXL7jEFoiqLEdmkvU= -github.com/openconfig/goyang v1.6.0 h1:JjnPbLY1/y28VyTO67LsEV0TaLWNiZyDcsppGq4F4is= -github.com/openconfig/goyang v1.6.0/go.mod h1:sdNZi/wdTZyLNBNfgLzmmbi7kISm7FskMDKKzMY+x1M= +github.com/openconfig/gnsi v1.9.0 h1:DokjN2rvzrP9/sMexBtigq6XkeKO8cPzzQmF8HQcVLQ= +github.com/openconfig/gnsi v1.9.0/go.mod h1:mvfo1wUBFfojkHrD8kKqVV8Epoyq1Vt1Qpkj2hif6ow= +github.com/openconfig/gocloser v0.0.0-20250211195114-79e08bd41eef h1:kwB7Jd38i9QfyIZCjQjQF8fBzF+AQeGV/vm4d1H9MzA= +github.com/openconfig/gocloser v0.0.0-20250211195114-79e08bd41eef/go.mod h1:tjeyxDYpFnYUTY6fnScQ8JqNoH4iMHEvWs8M9gj28GE= +github.com/openconfig/goyang v1.6.3 h1:9nWXBwd6b4+nZr8ni7O4zUXVhrVMXCLFz8os5YWFuo4= +github.com/openconfig/goyang v1.6.3/go.mod h1:5WolITjek1NF8yrNERyVZ7jqjOClJTpO8p/+OwmETM4= github.com/openconfig/gribi v1.9.0 h1:FOkhM/9c7DEHjS5FtlQqvp+mZYiSiME+SHNIB/u6tYI= github.com/openconfig/gribi v1.9.0/go.mod h1:bzoH3ROsWt/3TeNHukKGjsSMSAgY7VwOKtyzMegZL4E= -github.com/openconfig/gribigo v0.0.0-20240829231637-69cf06726cc3 h1:6w4kOXdXXLv3eASi3iXe3a0uHnhtrXmUx7fUqDt2FzA= -github.com/openconfig/gribigo v0.0.0-20240829231637-69cf06726cc3/go.mod h1:SVfLdNTmy/dIfScQFpljYKs0NGQ2n37h4GlZ9fVS+fA= -github.com/openconfig/grpctunnel v0.0.0-20220819142823-6f5422b8ca70/go.mod h1:OmTWe7RyZj2CIzIgy4ovEBzCLBJzRvWSZmn7u02U9gU= +github.com/openconfig/gribigo v0.1.1 h1:Taw7zDI2LO0JlPgxJewWJxGcNNdyJiD4bnsU6Umc1Gg= +github.com/openconfig/gribigo v0.1.1/go.mod h1:hoVByCuDRIH2zeA+x/LHt8XdQdG+iK2LPmXkGe2AhUY= github.com/openconfig/grpctunnel v0.1.0 h1:EN99qtlExZczgQgp5ANnHRC/Rs62cAG+Tz2BQ5m/maM= github.com/openconfig/grpctunnel v0.1.0/go.mod h1:G04Pdu0pml98tdvXrvLaU+EBo3PxYfI9MYqpvdaEHLo= github.com/openconfig/kne v0.1.18 h1:8D9SexWhj6knxfvEficyVj0F13GIvF1pQz7TKwVDSUI= github.com/openconfig/kne v0.1.18/go.mod h1:VMKjKI9FoVTLh4uN94uoaFZCp1CDkml2Ms2qOi1B2WM= -github.com/openconfig/lemming v0.4.1-0.20240731191322-a759a5e931a6 h1:MeZOAM3KyyJwCNRskjCuz9N1VXB20TPiOkyNYuZcbP8= -github.com/openconfig/lemming v0.4.1-0.20240731191322-a759a5e931a6/go.mod h1:mHnxyt20ewF4FznTqy+Op/CnCqXRNB7rJ/mm3wSJGxc= +github.com/openconfig/lemming v0.5.1 h1:Zk1HgdWDkDTYB/HSF9k17adB7wbUXlb38ss8MlVx0NQ= +github.com/openconfig/lemming v0.5.1/go.mod h1:X2ilf2qGdOoJ+cXiB1d/MjlulcnpDOCh3W8AHHWuhnU= github.com/openconfig/lemming/operator v0.2.0 h1:dovZnR6lQkOHXcODli1NDOr/GVYrBY05KS5X11jxVbw= github.com/openconfig/lemming/operator v0.2.0/go.mod h1:LKgEXSR5VK2CAeh2uKijKAXFj42uQuwakrCHVPF0iII= github.com/openconfig/models-ci v1.0.2-0.20231113233730-f0986391428e h1:6N4jXpZa/SXYcNpJFjjZvenxO/xnTwuUCgCEinhNLfU= github.com/openconfig/models-ci v1.0.2-0.20231113233730-f0986391428e/go.mod h1:w38G/kObu95PbtwMYVp6SKhkHCegJFwL8B58Ns84g4s= -github.com/openconfig/ondatra v0.9.1 h1:eiq7I7w5y3Paie2kP2vg1jWeOOXaTx2CD2ecqrV6fjc= -github.com/openconfig/ondatra v0.9.1/go.mod h1:qP+75EL6De0SDudX/WS6Vocl0r+qwxwhBiRZFCAbTYg= +github.com/openconfig/ondatra v0.12.2 h1:q7MmP551Q2SJAuv29i8fFx89RR3wWO5R+E5+DNwuWIw= +github.com/openconfig/ondatra v0.12.2/go.mod h1:HEyMGzKbgGDUfowayPpr45PcNz7QRjm8WI2q5R+2lcc= github.com/openconfig/replayer v0.0.0-20240110192655-4e9cf83d8d30 h1:KcHS08m7nFHq/D03ZfZKKNCSaS1jsuvdF3lCyDjPWJc= github.com/openconfig/replayer v0.0.0-20240110192655-4e9cf83d8d30/go.mod h1:VQ8FdPVaHwxKtamhcrwkPsvTeeoEgFYNK1xE8nHD0S8= github.com/openconfig/testt v0.0.0-20220311054427-efbb1a32ec07 h1:X631iD/B0ximGFb5P9LY5wHju4SiedxUhc5UZEo7VSw= github.com/openconfig/testt v0.0.0-20220311054427-efbb1a32ec07/go.mod h1:bmpU0kIsCiXuncozViVuQx1HqolC3C94H7lD9KKmoTo= github.com/openconfig/ygnmi v0.12.0 h1:tHQvwAtOK59P9o0yLYT9VcVFG90jPFebaKJTFk1M8V0= github.com/openconfig/ygnmi v0.12.0/go.mod h1:tgmeZPVofJnH0zUIPmoO0ZyBX3Ami1asv86k7cbahVA= -github.com/openconfig/ygot v0.6.0/go.mod h1:o30svNf7O0xK+R35tlx95odkDmZWS9JyWWQSmIhqwAs= -github.com/openconfig/ygot v0.29.20 h1:XHLpwCN91QuKc2LAvnEqtCmH8OuxgLlErDhrdl2mJw8= -github.com/openconfig/ygot v0.29.20/go.mod h1:K8HbrPm/v8/emtGQ9+RsJXx6UPKC5JzS/FqK7pN+tMo= +github.com/openconfig/ygot v0.33.0 h1:AHvMYdQpTHImpSvoPvzZkdsTAegX2yi0IpsLLuzohgY= +github.com/openconfig/ygot v0.33.0/go.mod h1:sMTAECRlEmETQ36XaAbWav5AFrR6EZlNNcHyZ7KHBDE= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/osrg/gobgp/v3 v3.27.1-0.20240614010451-0148e2d22dcf h1:KrVLbjNucHf+LrrGcwrH6hN0RyfmbPx9Vk5/iBsFfYY= github.com/osrg/gobgp/v3 v3.27.1-0.20240614010451-0148e2d22dcf/go.mod h1:ZGeSti9mURR/o5hf5R6T1FM5g1yiEBZbhP+TuqYJUpI= @@ -342,11 +348,10 @@ github.com/p4lang/p4runtime v1.4.1 h1:YdtDyDReeGEmSvuxqR8iefSTnttRSW5jWJWtpgCSFv github.com/p4lang/p4runtime v1.4.1/go.mod h1:OWAP4Wh9uKGnQjleslObpFE0REP78b5gR1pHyYmvNPQ= github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= -github.com/pborman/getopt v1.1.0/go.mod h1:FxXoW1Re00sQG/+KIkuSqRL/LwQgSkv7uyac+STFsbk= github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= github.com/pborman/uuid v1.2.1/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -367,7 +372,6 @@ github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI= github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY= -github.com/protocolbuffers/txtpbfmt v0.0.0-20220608084003-fc78c767cd6a/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= github.com/protocolbuffers/txtpbfmt v0.0.0-20240823084532-8e6b51fa9bef h1:ej+64jiny5VETZTqcc1GFVAPEtaSk6U1D0kKC2MS5Yc= github.com/protocolbuffers/txtpbfmt v0.0.0-20240823084532-8e6b51fa9bef/go.mod h1:jgxiZysxFPM+iWKwQwPR+y+Jvo54ARd4EisXxKYpB5c= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= @@ -376,10 +380,8 @@ github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/f github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk= -github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0= -github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= -github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/sagikazarmark/locafero v0.10.0 h1:FM8Cv6j2KqIhM2ZK7HZjm4mpj9NBktLgowT1aN9q5Cc= +github.com/sagikazarmark/locafero v0.10.0/go.mod h1:Ieo3EUsjifvQu4NZwV5sPd4dwvu0OCgEQV7vjc9yDjw= github.com/scrapli/scrapligo v1.0.0/go.mod h1:jvRMdb90MNnswMiku8UNXj8JZaOIPhwhcqqFwr9qeoY= github.com/scrapli/scrapligo v1.1.7/go.mod h1:rRx/rT2oNPYztiT3/ik0FRR/Ro7AdzN/eR9AtF8A81Y= github.com/scrapli/scrapligo v1.1.11 h1:ATvpF2LDoxnd/HlfSj5A0IiJDro75D6nuCx8m6S44vU= @@ -396,20 +398,20 @@ github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= -github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= -github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= +github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= -github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= -github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= -github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= -github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= -github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c h1:zqmyTlQyufRC65JnImJ6H1Sf7BDj8bG31EV919NVEQc= -github.com/spf13/pflag v1.0.6-0.20201009195203-85dd5c8bc61c/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= -github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= +github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= +github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= +github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE= github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g= github.com/srl-labs/srl-controller v0.6.1 h1:hHduqG41wglpeVPD85RALTwWWcS+NqvU8V1pHJMQIZo= @@ -446,36 +448,49 @@ github.com/yoheimuta/go-protoparser/v4 v4.9.0 h1:zHRXzRjkOamwMkPu7bpiCtOpxHkM9c8 github.com/yoheimuta/go-protoparser/v4 v4.9.0/go.mod h1:AHNNnSWnb0UoL4QgHPiOAg2BniQceFscPI5X/BZNHl8= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.4.13 h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zeebo/errs v1.4.0 h1:XNdoD/RRMKP7HD0UhJnIzUy74ISdGGxURlYG8HSWSfM= github.com/zeebo/errs v1.4.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= -go.einride.tech/aip v0.66.0 h1:XfV+NQX6L7EOYK11yoHHFtndeaWh3KbD9/cN/6iWEt8= -go.einride.tech/aip v0.66.0/go.mod h1:qAhMsfT7plxBX+Oy7Huol6YUvZ0ZzdUz26yZsQwfl1M= +go.einride.tech/aip v0.68.0 h1:4seM66oLzTpz50u4K1zlJyOXQ3tCzcJN7I22tKkjipw= +go.einride.tech/aip v0.68.0/go.mod h1:7y9FF8VtPWqpxuAxl0KQWqaULxW4zFIesD6zF5RIHHg= go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0 h1:4Pp6oUg3+e/6M4C0A/3kJ2VYa++dsWVTtGgLVj5xtHg= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.49.0/go.mod h1:Mjt1i1INqiaoZOMGR1RIUJN+i3ChKoFRqzrRQhlkbs0= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0 h1:CV7UdSGJt/Ao6Gp4CXckLxVRRsRgDHoI8XjbL3PDl8s= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.59.0/go.mod h1:FRmFuRJfag1IZ2dPkHnEoSFVgTVPUd2qf5Vi69hLb8I= -go.opentelemetry.io/otel v1.34.0 h1:zRLXxLCgL1WyKsPVrgbSdMN4c0FMkDAskSTQP+0hdUY= -go.opentelemetry.io/otel v1.34.0/go.mod h1:OWFPOQ+h4G8xpyjgqo4SxJYdDQ/qmRH+wivy7zzx9oI= -go.opentelemetry.io/otel/metric v1.34.0 h1:+eTR3U0MyfWjRDhmFMxe2SsW64QrZ84AOhvqS7Y+PoQ= -go.opentelemetry.io/otel/metric v1.34.0/go.mod h1:CEDrp0fy2D0MvkXE+dPV7cMi8tWZwX3dmaIhwPOaqHE= -go.opentelemetry.io/otel/sdk v1.34.0 h1:95zS4k/2GOy069d321O8jWgYsW3MzVV+KuSPKp7Wr1A= -go.opentelemetry.io/otel/sdk v1.34.0/go.mod h1:0e/pNiaMAqaykJGKbi+tSjWfNNHMTxoC9qANsCzbyxU= -go.opentelemetry.io/otel/sdk/metric v1.34.0 h1:5CeK9ujjbFVL5c1PhLuStg1wxA7vQv7ce1EK0Gyvahk= -go.opentelemetry.io/otel/sdk/metric v1.34.0/go.mod h1:jQ/r8Ze28zRKoNRdkjCZxfs6YvBTG1+YIqyFVFYec5w= -go.opentelemetry.io/otel/trace v1.34.0 h1:+ouXS2V8Rd4hp4580a8q23bg0azF2nI8cqLYnC8mh/k= -go.opentelemetry.io/otel/trace v1.34.0/go.mod h1:Svm7lSjQD7kG7KJ/MUHPVXSDGz2OX4h0M2jHBhmSfRE= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0 h1:F7q2tNlCaHY9nMKHR6XH9/qkp8FktLnIcy6jJNyOCQw= +go.opentelemetry.io/contrib/detectors/gcp v1.36.0/go.mod h1:IbBN8uAIIx734PTonTPxAxnjc2pQTxWNkwfstZ+6H2k= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= +go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= +go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0 h1:bDMKF3RUSxshZ5OjOTi8rsHGaPKsAt76FaqgvIUySLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.37.0/go.mod h1:dDT67G/IkA46Mr2l9Uj7HsQVwsjASyV9SjGofsiUZDA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0 h1:WDdP9acbMYjbKIyJUhTvtzj601sVJOqgWdUxSdR/Ysc= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.29.0/go.mod h1:BLbf7zbNIONBLPwvFnwNHGj4zge8uTCM/UPIVW1Mq2I= +go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= +go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= +go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= +go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= +go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= +go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= +go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= +go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= @@ -489,21 +504,19 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.0.0-20220518034528-6f7dac969898/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU= -golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b h1:DXr+pvt3nC887026GRP39Ej11UATqWDmWuS99x26cD0= +golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -522,22 +535,21 @@ golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qx golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.26.0 h1:afQXWNNaeC4nvZ0Ed9XvCCzXM6UHJG7iCg0W4fPqSBE= -golang.org/x/oauth2 v0.26.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= -golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -551,29 +563,28 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= +golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -586,20 +597,19 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= -golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= -golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= gomodules.xyz/jsonpatch/v2 v2.2.0 h1:4pT439QV83L+G9FkcCriY6EkpcK6r6bK+A5FBUMI7qY= gomodules.xyz/jsonpatch/v2 v2.2.0/go.mod h1:WXp+iVDkoLQqPudfQ9GBlwB2eZ5DKOnjQZCYdOS8GPY= -google.golang.org/api v0.171.0 h1:w174hnBPqut76FzW5Qaupt7zY8Kql6fiVjgys4f58sU= -google.golang.org/api v0.171.0/go.mod h1:Hnq5AHm4OTMt2BUVjael2CWZFD6vksJdWCWiUAmjC9o= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/api v0.215.0 h1:jdYF4qnyczlEz2ReWIsosNLDuzXyvFHJtI5gcr0J7t0= +google.golang.org/api v0.215.0/go.mod h1:fta3CVtuJYOEdugLNWm6WodzOS8KdFckABwN4I40hzY= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -607,14 +617,13 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/genproto v0.0.0-20200423170343-7949de9c1215/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20210811021853-ddbe55d93216/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20220107163113-42d7afdf6368/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY= -google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de/go.mod h1:VUhTRKeHn9wwcdrk73nvdC9gF178Tzhmt/qyaFcPLSo= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a h1:51aaUVRocpvUOSQKM6Q7VuoaktNIaMCLuhZB6DKksq4= -google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a/go.mod h1:uRxBH1mhmO8PGhU89cMcHaXKZqO+OfakD8QQO0oYwlQ= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 h1:ToEetK57OidYuqD4Q5w+vfEnPvPpuTwedCNVohYJfNk= +google.golang.org/genproto v0.0.0-20241118233622-e639e219e697/go.mod h1:JJrvXBWRZaFMxBufik1a4RpFw4HhgVtBBWQeQgUj2cc= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c h1:AtEkQdl5b6zsybXcbz00j1LwNodDuH6hVifIaNqk7NQ= +google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c/go.mod h1:ea2MjsO70ssTfCjiwHgI0ZFqcw45Ksuk2ckf9G468GA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c h1:qXWI/sQtv5UKboZ/zUk7h+mrf/lXORyI+n9DKDAusdg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250818200422-3122310a409c/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -623,11 +632,9 @@ google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3Iji google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.72.0 h1:S7UkcVa60b5AAQTaO6ZKamFp1zMZSU0fGDK2WZLbBnM= -google.golang.org/grpc v1.72.0/go.mod h1:wH5Aktxcg25y1I3w7H69nHfXdOG3UiadoBtjh3izSDM= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -640,9 +647,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -651,8 +657,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/attrs/attrs.go b/internal/attrs/attrs.go index d1444ebd7af..cab9b372615 100644 --- a/internal/attrs/attrs.go +++ b/internal/attrs/attrs.go @@ -37,6 +37,7 @@ type Attributes struct { IPv4 string IPv4Sec string // Secondary IPv4 address IPv6 string + IPv6Sec string // Secondary IPv6 address MAC string Name string // Interface name, only applied to ATE ports. Desc string // Description, only applied to DUT interfaces. diff --git a/internal/cfgplugins/bgp.go b/internal/cfgplugins/bgp.go index ccd39f5cd60..17572d7823e 100644 --- a/internal/cfgplugins/bgp.go +++ b/internal/cfgplugins/bgp.go @@ -25,6 +25,7 @@ import ( "github.com/openconfig/featureprofiles/internal/deviations" "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/featureprofiles/internal/otgutils" + gnmipb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -554,3 +555,70 @@ func VerifyPortsUp(t *testing.T, dev *ondatra.Device) { } } } + +// DeviationAristaBGPNeighborMaxPrefixes updates the max-prefixes of a specific BGP neighbor. +// This is an Arista specific augmented model which sets the following path: +// /network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/prefix-limit/config/max-prefixes +// Set max-prefixes to 0 will mean no limit will be set. +// Tracking the removal of this deviation in b/438620249 +func DeviationAristaBGPNeighborMaxPrefixes(t *testing.T, dut *ondatra.DUTDevice, neighborIP string, maxPrefixes uint32) { + gpbSetRequest := &gnmipb.SetRequest{ + Update: []*gnmipb.Update{{ + Path: &gnmipb.Path{ + Elem: []*gnmipb.PathElem{ + {Name: "network-instances"}, + {Name: "network-instance", Key: map[string]string{"name": deviations.DefaultNetworkInstance(dut)}}, + {Name: "protocols"}, + {Name: "protocol", Key: map[string]string{"name": "BGP", "identifier": "BGP"}}, + {Name: "bgp"}, + {Name: "neighbors"}, + {Name: "neighbor", Key: map[string]string{"neighbor-address": neighborIP}}, + {Name: "prefix-limit"}, + {Name: "config"}, + {Name: "max-prefixes"}, + }, + }, + Val: &gnmipb.TypedValue{ + Value: &gnmipb.TypedValue_UintVal{ + UintVal: uint64(maxPrefixes), + }, + }, + }}, + } + gnmiClient := dut.RawAPIs().GNMI(t) + if _, err := gnmiClient.Set(t.Context(), gpbSetRequest); err != nil { + t.Errorf("Unexpected error max-prefix: %v", err) + } +} + +func ConfigureBGPNeighbor(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance, routerId, peerAddress string, routerAS, peerAS uint32, ipType string, sendReceivePaths bool) { + if ni == nil { + t.Fatalf("Network Instance is not configured") + } + proto := ni.GetOrCreateProtocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP") + bgp := proto.GetOrCreateBgp() + global := bgp.GetOrCreateGlobal() + global.As = ygot.Uint32(routerAS) + global.RouterId = ygot.String(routerId) + + neighbor := bgp.GetOrCreateNeighbor(peerAddress) + neighbor.PeerAs = ygot.Uint32(peerAS) + neighbor.Enabled = ygot.Bool(true) + neighbor.SendCommunityType = []oc.E_Bgp_CommunityType{oc.Bgp_CommunityType_NONE} + + neighbor.GetOrCreateApplyPolicy().DefaultExportPolicy = oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE + neighbor.GetOrCreateApplyPolicy().DefaultImportPolicy = oc.RoutingPolicy_DefaultPolicyType_ACCEPT_ROUTE + + var nAfiSafi *oc.NetworkInstance_Protocol_Bgp_Neighbor_AfiSafi + switch ipType { + case IPv4: + nAfiSafi = neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV4_UNICAST) + nAfiSafi.GetOrCreateIpv4Unicast().SendDefaultRoute = ygot.Bool(true) + case IPv6: + nAfiSafi = neighbor.GetOrCreateAfiSafi(oc.BgpTypes_AFI_SAFI_TYPE_IPV6_UNICAST) + nAfiSafi.GetOrCreateIpv6Unicast().SendDefaultRoute = ygot.Bool(true) + } + nAfiSafi.Enabled = ygot.Bool(true) + nAfiSafi.GetOrCreateAddPaths().Receive = ygot.Bool(sendReceivePaths) + nAfiSafi.GetOrCreateAddPaths().Send = ygot.Bool(sendReceivePaths) +} diff --git a/internal/cfgplugins/bgp_policy.go b/internal/cfgplugins/bgp_policy.go index 2bebe956f5f..28f71ccbde9 100644 --- a/internal/cfgplugins/bgp_policy.go +++ b/internal/cfgplugins/bgp_policy.go @@ -78,3 +78,19 @@ func DeviationJuniperRoutingPolicyBGPActionSetCommunity(t *testing.T, dut *ondat }`, policyName, statement, community) helpers.GnmiCLIConfig(t, dut, config) } + +// DeviationAristaRoutingPolicyBGPAsPathSetUnsupported is used for DUTs that don't support filtering by AS-Set (in tests such as RT-1.64) +// This deviation implements CLI to perform the same function +func DeviationAristaRoutingPolicyBGPAsPathSetUnsupported(t *testing.T, dut *ondatra.DUTDevice, aclName string, routeMap string, asPathRegex string) { + // ip as-path access-list "aclName" permit "asPathRegex" + // ip as-path access-list "aclName" deny .* + // route-map "routeMap" "sequence" + // match as-path "aclName" + config := fmt.Sprintf(` +ip as-path access-list %s permit %s +ip as-path access-list %s deny .* +route-map %s +match as-path %s +`, aclName, asPathRegex, aclName, routeMap, aclName) + helpers.GnmiCLIConfig(t, dut, config) +} diff --git a/internal/cfgplugins/dut_initialize.go b/internal/cfgplugins/dut_initialize.go new file mode 100644 index 00000000000..da97a2fa789 --- /dev/null +++ b/internal/cfgplugins/dut_initialize.go @@ -0,0 +1,415 @@ +// Copyright 2025 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 cfgplugins + +import ( + "context" + "strings" + "testing" + + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ondatra" +) + +type FeatureType int + +const ( + FeatureMplsTracking FeatureType = iota + FeatureVrfSelectionExtended + FeaturePolicyForwarding + + aristaTcamProfileMplsTracking = ` +hardware counter feature traffic-policy in +! +hardware tcam + profile ancx + feature acl port ip + sequence 45 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops l4-src-port src-ip tcp-control ttl + action count drop mirror + packet ipv4 forwarding bridged + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + packet ipv4 vxlan eth ipv4 forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + feature acl port ip egress mpls-tunnelled-match + sequence 95 + feature acl port ipv6 + sequence 25 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-ops-3b l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop mirror + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + packet ipv6 forwarding routed multicast + packet ipv6 ipv6 forwarding routed decap + feature acl port ipv6 egress + sequence 105 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop mirror + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + feature acl port mac + sequence 55 + key size limit 160 + key field dst-mac ether-type src-mac + action count drop mirror + packet ipv4 forwarding bridged + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + packet ipv6 forwarding routed decap + packet ipv6 forwarding routed multicast + packet ipv6 ipv6 forwarding routed decap + packet mpls forwarding bridged decap + packet mpls ipv4 forwarding mpls + packet mpls ipv6 forwarding mpls + packet mpls non-ip forwarding mpls + packet non-ip forwarding bridged + feature acl vlan ipv6 egress + sequence 20 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop mirror + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + feature counter lfib + sequence 85 + feature forwarding-destination mpls + sequence 100 + feature mirror ip + sequence 80 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops l4-src-port src-ip tcp-control + action count mirror set-policer + packet ipv4 forwarding bridged + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 non-vxlan forwarding routed decap + feature mpls + sequence 5 + key size limit 160 + action drop redirect set-ecn + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet mpls ipv4 forwarding mpls + packet mpls ipv6 forwarding mpls + packet mpls non-ip forwarding mpls + feature mpls pop ingress + sequence 90 + feature pbr mpls + sequence 65 + key size limit 160 + key field mpls-inner-ip-tos + action count drop redirect + packet mpls ipv4 forwarding mpls + packet mpls ipv6 forwarding mpls + packet mpls non-ip forwarding mpls + feature qos ip + sequence 75 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops l4-src-port src-ip tcp-control + action set-dscp set-policer set-tc + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + feature qos ipv6 + sequence 70 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-src-port src-ipv6-high src-ipv6-low + action set-dscp set-policer set-tc + packet ipv6 forwarding routed + feature traffic-policy port ipv4 + sequence 45 + key size limit 160 + key field dscp dst-ip-label ip-frag ip-fragment-offset ip-length ip-protocol l4-dst-port-label l4-src-port-label src-ip-label tcp-control ttl + action count drop redirect set-dscp set-tc + packet ipv4 forwarding routed + feature traffic-policy port ipv4 egress + key size limit 160 + key field dscp dst-ip-label ip-frag ip-protocol l4-dst-port-label l4-src-port-label src-ip-label + action count drop + packet ipv4 forwarding routed + feature traffic-policy port ipv6 + sequence 25 + key size limit 160 + key field dst-ipv6-label hop-limit ipv6-length ipv6-next-header ipv6-traffic-class l4-dst-port-label l4-src-port-label src-ipv6-label tcp-control + action count drop redirect set-dscp set-tc + packet ipv6 forwarding routed + feature traffic-policy port ipv6 egress + key size limit 160 + key field dscp dst-ipv6-label ipv6-next-header l4-dst-port-label l4-src-port-label src-ipv6-label + action count drop + packet ipv6 forwarding routed + feature tunnel vxlan + sequence 50 + key size limit 160 + packet ipv4 vxlan eth ipv4 forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + system profile ancx +! +` + + aristaTcamProfileVrfSelectionExtended = ` +hardware tcam + profile vrf-selection-with-ip6-sip + feature acl port ip + sequence 45 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops l4-src-port src-ip tcp-control ttl + action count drop mirror + packet ipv4 forwarding bridged + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + packet ipv4 vxlan eth ipv4 forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + feature acl port ip egress mpls-tunnelled-match + sequence 95 + feature acl port ipv6 + sequence 25 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-ops-3b l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop mirror + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + packet ipv6 forwarding routed multicast + packet ipv6 ipv6 forwarding routed decap + feature acl port ipv6 egress + sequence 105 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop mirror + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + feature acl port mac + sequence 55 + key size limit 160 + key field dst-mac ether-type src-mac + action count drop mirror + packet ipv4 forwarding bridged + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + packet ipv6 forwarding routed decap + packet ipv6 forwarding routed multicast + packet ipv6 ipv6 forwarding routed decap + packet mpls forwarding bridged decap + packet mpls ipv4 forwarding mpls + packet mpls ipv6 forwarding mpls + packet mpls non-ip forwarding mpls + packet non-ip forwarding bridged + feature acl subintf ip + sequence 40 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops-18b l4-src-port src-ip tcp-control ttl + action count drop + packet ipv4 forwarding routed + feature acl subintf ipv6 + sequence 15 + key field dst-ipv6 ipv6-next-header l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop + packet ipv6 forwarding routed + feature acl vlan ip + sequence 35 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops-18b l4-src-port src-ip tcp-control ttl + action count drop + packet ipv4 forwarding routed + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + packet ipv4 vxlan eth ipv4 forwarding routed decap + feature acl vlan ipv6 + sequence 10 + key field dst-ipv6 ipv6-next-header l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop + packet ipv6 forwarding routed + packet ipv6 ipv6 forwarding routed decap + feature acl vlan ipv6 egress + sequence 20 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count drop mirror + packet ipv6 forwarding bridged + packet ipv6 forwarding routed + feature counter lfib + sequence 85 + feature forwarding-destination mpls + sequence 100 + feature mirror ip + sequence 80 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops l4-src-port src-ip tcp-control + action count mirror set-policer + packet ipv4 forwarding bridged + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 non-vxlan forwarding routed decap + feature mpls + sequence 5 + key size limit 160 + action drop redirect set-ecn + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet mpls ipv4 forwarding mpls + packet mpls ipv6 forwarding mpls + packet mpls non-ip forwarding mpls + feature mpls pop ingress + sequence 90 + feature pbr ip + sequence 60 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops-18b l4-src-port src-ip tcp-control + action count redirect + packet ipv4 forwarding routed + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + feature pbr ipv6 + sequence 30 + key field dst-ipv6 ipv6-next-header l4-dst-port l4-src-port src-ipv6-high src-ipv6-low tcp-control + action count redirect + packet ipv6 forwarding routed + feature pbr mpls + sequence 65 + key size limit 160 + key field mpls-inner-ip-tos + action count drop redirect + packet mpls ipv4 forwarding mpls + packet mpls ipv6 forwarding mpls + packet mpls non-ip forwarding mpls + feature qos ip + sequence 75 + key size limit 160 + key field dscp dst-ip ip-frag ip-protocol l4-dst-port l4-ops l4-src-port src-ip tcp-control + action set-dscp set-policer set-tc + packet ipv4 forwarding routed + packet ipv4 forwarding routed multicast + packet ipv4 mpls ipv4 forwarding mpls decap + packet ipv4 mpls ipv6 forwarding mpls decap + packet ipv4 non-vxlan forwarding routed decap + feature qos ipv6 + sequence 70 + key field dst-ipv6 ipv6-next-header ipv6-traffic-class l4-dst-port l4-src-port src-ipv6-high src-ipv6-low + action set-dscp set-policer set-tc + packet ipv6 forwarding routed + feature tunnel vxlan + sequence 50 + key size limit 160 + packet ipv4 vxlan eth ipv4 forwarding routed decap + packet ipv4 vxlan forwarding bridged decap + feature vrf selection + port qualifier size 8 bits + feature vrf selection extended + ! + system profile vrf-selection-with-ip6-sip +` + aristaTcamProfilePolicyForwarding = ` + hardware tcam + profile tcam-policy-forwarding + feature traffic-policy port ipv4 + sequence 45 + key size limit 160 + key field dscp dst-ip-label ip-frag ip-fragment-offset ip-length ip-protocol l4-dst-port-label l4-src-port-label src-ip-label tcp-control ttl + action count drop redirect set-dscp set-tc + packet ipv4 forwarding routed + ! + feature traffic-policy port ipv6 + sequence 25 + key size limit 160 + key field dst-ipv6-label hop-limit ipv6-length ipv6-next-header ipv6-traffic-class l4-dst-port-label l4-src-port-label src-ipv6-label tcp-control + action count drop redirect set-dscp set-tc + packet ipv6 forwarding routed + ! + system profile tcam-policy-forwarding + ! + hardware counter feature gre tunnel interface out + ! + hardware counter feature traffic-policy in + ! + hardware counter feature traffic-policy out + ! + hardware counter feature route ipv4 + ! + hardware counter feature nexthop + ! + ` +) + +var ( + aristaTcamProfileMap = map[FeatureType]string{ + FeatureMplsTracking: aristaTcamProfileMplsTracking, + FeatureVrfSelectionExtended: aristaTcamProfileVrfSelectionExtended, + FeaturePolicyForwarding: aristaTcamProfilePolicyForwarding, + } +) + +func buildCliSetRequest(config string) *gpb.SetRequest { + gpbSetRequest := &gpb.SetRequest{ + Update: []*gpb.Update{ + { + Path: &gpb.Path{ + Origin: "cli", + Elem: []*gpb.PathElem{}, + }, + Val: &gpb.TypedValue{ + Value: &gpb.TypedValue_AsciiVal{ + AsciiVal: config, + }, + }, + }, + }, + } + return gpbSetRequest +} + +func NewDUTHardwareInit(t *testing.T, dut *ondatra.DUTDevice, feature FeatureType) string { + switch dut.Vendor() { + case ondatra.ARISTA: + if strings.ToLower(dut.Model()) == "ceos" { + return "" + } + return aristaTcamProfileMap[feature] + default: + return "" + } +} + +func PushDUTHardwareInitConfig(t *testing.T, dut *ondatra.DUTDevice, hardwareInitConf string) { + if hardwareInitConf == "" { + t.Logf("No hardware init config provided") + return + } + gnmiClient := dut.RawAPIs().GNMI(t) + t.Log("Pushing hardware init config") + gpbSetRequest := buildCliSetRequest(hardwareInitConf) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Fatalf("Failed to set hardware init config: %v", err) + } +} diff --git a/internal/cfgplugins/interface.go b/internal/cfgplugins/interface.go index a172103a3de..98f63e9cbae 100644 --- a/internal/cfgplugins/interface.go +++ b/internal/cfgplugins/interface.go @@ -15,12 +15,17 @@ package cfgplugins import ( + "fmt" "math" + "sort" + "strconv" + "strings" "sync" "testing" "github.com/openconfig/featureprofiles/internal/components" "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" "github.com/openconfig/ondatra" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" @@ -35,8 +40,15 @@ const ( ) var ( - opmode uint16 - once sync.Once + opmode uint16 + once sync.Once + lBandPNs = map[string]bool{ + "DP04QSDD-LLH-240": true, // Cisco QSFPDD Acacia 400G ZRP L-Band + "DP04QSDD-LLH-00A": true, // Cisco QSFPDD Acacia 400G ZRP L-Band + "DP08SFP8-LRB-240": true, // Cisco OSFP Acacia 800G ZRP L-Band + "C-OS08LEXNC-GG": true, // Nokia OSFP 800G ZRP L-Band + "176-6490-9G1": true, // Ciena OSFP 800G ZRP L-Band + } ) // Temporary code for assigning opmode 1 maintained until opmode is Initialized in all .go file @@ -44,7 +56,450 @@ func init() { opmode = 1 } -// Initialize assigns OpMode with value received through operationalMode flag. +// OperationalModeList is a type for a list of operational modes in uint16 format. +type OperationalModeList []uint16 + +// String returns the string representation of the list of operational modes. +func (om *OperationalModeList) String() string { + var s []string + for _, v := range *om { + s = append(s, fmt.Sprintf("%v", v)) + } + return strings.Join(s, ",") +} + +// Set sets the list of operational modes from the string representation. +func (om *OperationalModeList) Set(value string) error { + for _, s := range strings.Split(value, ",") { + if v, err := strconv.ParseUint(s, 10, 16); err != nil { + return err + } else { + *om = append(*om, uint16(v)) + } + } + return nil +} + +// Get returns the list of operational modes. +func (om *OperationalModeList) Get() any { + return *om +} + +// Default returns the default operational mode list. +func (om *OperationalModeList) Default(t *testing.T, dut *ondatra.DUTDevice) OperationalModeList { + t.Helper() + p := dut.Ports()[0] + switch p.PMD() { + case ondatra.PMD400GBASEZR: // 400G (8x56G) + switch dut.Vendor() { + case ondatra.CISCO: + return OperationalModeList{5003} + case ondatra.ARISTA, ondatra.JUNIPER: + return OperationalModeList{1} + case ondatra.NOKIA: + return OperationalModeList{1083} + default: + t.Fatalf("Unsupported vendor: %v", dut.Vendor()) + } + case ondatra.PMD400GBASEZRP: // 400G (8x56G) + switch dut.Vendor() { + case ondatra.CISCO: + return OperationalModeList{6004} + case ondatra.ARISTA, ondatra.JUNIPER, ondatra.NOKIA: + return OperationalModeList{4} + default: + t.Fatalf("Unsupported vendor: %v", dut.Vendor()) + } + case ondatra.PMD800GBASEZR: + return OperationalModeList{1, 3} // 800G : 1 (8x112G), 400G : 3 (4x112G) + case ondatra.PMD800GBASEZRP: + return OperationalModeList{8, 4} // 800G : 8 (8x112G), 400G : 4 (4x112G) + default: + t.Fatalf("Unsupported PMD type: %v", p.PMD()) + } + return nil +} + +// FrequencyList is a type for a list of frequencies in uint64 format. +type FrequencyList []uint64 + +// String returns the string representation of the list of frequencies. +func (f *FrequencyList) String() string { + var s []string + for _, v := range *f { + s = append(s, fmt.Sprintf("%v", v)) + } + return strings.Join(s, ",") +} + +// Set sets the list of frequencies from the string representation. +func (f *FrequencyList) Set(value string) error { + for _, s := range strings.Split(value, ",") { + if v, err := strconv.ParseUint(s, 10, 64); err != nil { + return err + } else { + *f = append(*f, v) + } + } + return nil +} + +// Get returns the list of frequencies. +func (f *FrequencyList) Get() any { + return *f +} + +// Default returns the default frequency list. +func (f *FrequencyList) Default(t *testing.T, dut *ondatra.DUTDevice) FrequencyList { + t.Helper() + p := dut.Ports()[0] + switch p.PMD() { + case ondatra.PMD400GBASEZR, ondatra.PMD800GBASEZR: + return FrequencyList{196100000} + case ondatra.PMD400GBASEZRP, ondatra.PMD800GBASEZRP: + tr, present := gnmi.Lookup(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()).Val() + if !present { + t.Fatalf("Transceiver not found for port %v", p.Name()) + } + pn, present := gnmi.Lookup(t, dut, gnmi.OC().Component(tr).PartNo().State()).Val() + switch { + case present && lBandPNs[pn]: + return FrequencyList{190000000} + default: + return FrequencyList{196100000} + } + default: + t.Fatalf("Unsupported PMD type: %v", p.PMD()) + } + return nil +} + +// TargetOpticalPowerList is a type for a list of target optical powers in float64 format. +type TargetOpticalPowerList []float64 + +// String returns the string representation of the list of target optical powers. +func (top *TargetOpticalPowerList) String() string { + var s []string + for _, v := range *top { + s = append(s, fmt.Sprintf("%v", v)) + } + return strings.Join(s, ",") +} + +// Set sets the list of target optical powers from the string representation. +func (top *TargetOpticalPowerList) Set(value string) error { + for _, s := range strings.Split(value, ",") { + if v, err := strconv.ParseFloat(s, 64); err != nil { + return err + } else { + *top = append(*top, v) + } + } + return nil +} + +// Get returns the list of target optical powers. +func (top *TargetOpticalPowerList) Get() any { + return *top +} + +// Default returns the default target optical power list. +func (top *TargetOpticalPowerList) Default(t *testing.T, dut *ondatra.DUTDevice) TargetOpticalPowerList { + t.Helper() + p := dut.Ports()[0] + switch p.PMD() { + case ondatra.PMD400GBASEZR: + return TargetOpticalPowerList{-10} + case ondatra.PMD400GBASEZRP: + return TargetOpticalPowerList{-7} + case ondatra.PMD800GBASEZR: + return TargetOpticalPowerList{-7} + case ondatra.PMD800GBASEZRP: + return TargetOpticalPowerList{-4} + default: + t.Fatalf("Unsupported PMD type: %v", p.PMD()) + } + return nil +} + +// AssignOTNIndexes assigns the OTN indexes for the given ports. +func AssignOTNIndexes(t *testing.T, dut *ondatra.DUTDevice) map[string]uint32 { + ports := dut.Ports() + sort.Slice(ports, func(i, j int) bool { + return ports[i].Name() < ports[j].Name() + }) + otnIndexes := make(map[string]uint32) + for idx, p := range ports { + switch p.PMD() { + case ondatra.PMD400GBASEZR, ondatra.PMD400GBASEZRP: + otnIndexes[p.Name()] = 4000 + uint32(idx) + case ondatra.PMD800GBASEZR, ondatra.PMD800GBASEZRP: + otnIndexes[p.Name()] = 8000 + uint32(idx) + default: + t.Fatalf("Unsupported PMD type for %v", p.PMD()) + } + } + return otnIndexes +} + +// AssignETHIndexes assigns the ETH indexes for the given ports. +func AssignETHIndexes(t *testing.T, dut *ondatra.DUTDevice) map[string]uint32 { + ports := dut.Ports() + sort.Slice(ports, func(i, j int) bool { + return ports[i].Name() < ports[j].Name() + }) + ethIndexes := make(map[string]uint32) + for idx, p := range ports { + switch p.PMD() { + case ondatra.PMD400GBASEZR, ondatra.PMD400GBASEZRP: + ethIndexes[p.Name()] = 40000 + uint32(idx) + case ondatra.PMD800GBASEZR, ondatra.PMD800GBASEZRP: + ethIndexes[p.Name()] = 80000 + uint32(idx) + default: + t.Fatalf("Unsupported PMD type for %v", p.PMD()) + } + } + return ethIndexes +} + +// ConfigParameters contains the configuration parameters for the ports. +type ConfigParameters struct { + Enabled bool + Frequency uint64 + TargetOpticalPower float64 + OperationalMode uint16 + PortSpeed oc.E_IfEthernet_ETHERNET_SPEED + FormFactor oc.E_TransportTypes_TRANSCEIVER_FORM_FACTOR_TYPE + NumPhysicalChannels uint8 + RateClass oc.E_TransportTypes_TRIBUTARY_RATE_CLASS_TYPE + TribProtocol oc.E_TransportTypes_TRIBUTARY_PROTOCOL_TYPE + Allocation float64 + HWPortNames map[string]string + TransceiverNames map[string]string + OpticalChannelNames map[string]string + OTNIndexes map[string]uint32 + ETHIndexes map[string]uint32 +} + +// NewInterfaceConfigAll configures all the ports. +func NewInterfaceConfigAll(t *testing.T, dut *ondatra.DUTDevice, batch *gnmi.SetBatch, params *ConfigParameters) { + t.Helper() + params.HWPortNames = make(map[string]string) + params.TransceiverNames = make(map[string]string) + params.OpticalChannelNames = make(map[string]string) + for _, p := range dut.Ports() { + if hwPortName, ok := gnmi.Lookup(t, dut, gnmi.OC().Interface(p.Name()).HardwarePort().State()).Val(); !ok { + t.Fatalf("Hardware port not found for %v", p.Name()) + } else { + params.HWPortNames[p.Name()] = hwPortName + } + if transceiverName, ok := gnmi.Lookup(t, dut, gnmi.OC().Interface(p.Name()).Transceiver().State()).Val(); !ok { + t.Fatalf("Transceiver not found for %v", p.Name()) + } else { + params.TransceiverNames[p.Name()] = transceiverName + } + params.OpticalChannelNames[p.Name()] = components.OpticalChannelComponentFromPort(t, dut, p) + params.OTNIndexes = AssignOTNIndexes(t, dut) + params.ETHIndexes = AssignETHIndexes(t, dut) + switch p.PMD() { + case ondatra.PMD400GBASEZR, ondatra.PMD400GBASEZRP: + params.FormFactor = oc.TransportTypes_TRANSCEIVER_FORM_FACTOR_TYPE_QSFP56_DD + params.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_400GB + params.NumPhysicalChannels = 8 + params.RateClass = oc.TransportTypes_TRIBUTARY_RATE_CLASS_TYPE_TRIB_RATE_400G + params.TribProtocol = oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_400GE + params.Allocation = 400 + case ondatra.PMD800GBASEZR, ondatra.PMD800GBASEZRP: + params.FormFactor = oc.TransportTypes_TRANSCEIVER_FORM_FACTOR_TYPE_OSFP + switch params.OperationalMode { + case 1, 2: + params.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_800GB + params.NumPhysicalChannels = 8 + params.RateClass = oc.TransportTypes_TRIBUTARY_RATE_CLASS_TYPE_TRIB_RATE_800G + params.TribProtocol = oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_800GE + params.Allocation = 800 + case 3, 4: + params.PortSpeed = oc.IfEthernet_ETHERNET_SPEED_SPEED_400GB + params.NumPhysicalChannels = 4 + params.RateClass = oc.TransportTypes_TRIBUTARY_RATE_CLASS_TYPE_TRIB_RATE_400G + params.TribProtocol = oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_400GE + params.Allocation = 400 + default: + t.Fatalf("Unsupported operational mode for %v: %v", p.PMD(), params.OperationalMode) + } + default: + t.Fatalf("Unsupported PMD type for %v", p.PMD()) + } + updateInterfaceConfig(batch, p, params) + updateHWPortConfig(batch, p, params) + updateOpticalChannelConfig(batch, p, params) + updateOTNChannelConfig(batch, dut, p, params) + updateETHChannelConfig(batch, dut, p, params) + } +} + +// updateInterfaceConfig updates the interface config. +func updateInterfaceConfig(batch *gnmi.SetBatch, p *ondatra.Port, params *ConfigParameters) { + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(params.Enabled), + } + if p.PMD() == ondatra.PMD800GBASEZR || p.PMD() == ondatra.PMD800GBASEZRP { + i.Ethernet = &oc.Interface_Ethernet{ + PortSpeed: params.PortSpeed, + DuplexMode: oc.Ethernet_DuplexMode_FULL, + } + } + gnmi.BatchReplace(batch, gnmi.OC().Interface(p.Name()).Config(), i) +} + +// updateHWPortConfig updates the hardware port config. +func updateHWPortConfig(batch *gnmi.SetBatch, p *ondatra.Port, params *ConfigParameters) { + if p.PMD() == ondatra.PMD400GBASEZR || p.PMD() == ondatra.PMD400GBASEZRP { + return // No HwPort config for 400GZR/400GZR Plus. + } + gnmi.BatchReplace(batch, gnmi.OC().Component(params.HWPortNames[p.Name()]).Config(), &oc.Component{ + Name: ygot.String(params.HWPortNames[p.Name()]), + Port: &oc.Component_Port{ + BreakoutMode: &oc.Component_Port_BreakoutMode{ + Group: map[uint8]*oc.Component_Port_BreakoutMode_Group{ + 1: { + Index: ygot.Uint8(1), + BreakoutSpeed: params.PortSpeed, + NumBreakouts: ygot.Uint8(1), + NumPhysicalChannels: ygot.Uint8(params.NumPhysicalChannels), + }, + }, + }, + }, + }) +} + +// updateOpticalChannelConfig updates the optical channel config. +func updateOpticalChannelConfig(batch *gnmi.SetBatch, p *ondatra.Port, params *ConfigParameters) { + + gnmi.BatchReplace(batch, gnmi.OC().Component(params.OpticalChannelNames[p.Name()]).Config(), &oc.Component{ + Name: ygot.String(params.OpticalChannelNames[p.Name()]), + OpticalChannel: &oc.Component_OpticalChannel{ + OperationalMode: ygot.Uint16(params.OperationalMode), + Frequency: ygot.Uint64(params.Frequency), + TargetOutputPower: ygot.Float64(params.TargetOpticalPower), + }, + }) +} + +// updateOTNChannelConfig updates the OTN channel config. +func updateOTNChannelConfig(batch *gnmi.SetBatch, dut *ondatra.DUTDevice, p *ondatra.Port, params *ConfigParameters) { + var firstAssignmentIndex uint32 + if deviations.OTNChannelAssignmentCiscoNumbering(dut) { + firstAssignmentIndex = 1 + } else { + firstAssignmentIndex = 0 + } + if deviations.OTNToETHAssignment(dut) { + gnmi.BatchReplace(batch, gnmi.OC().TerminalDevice().Channel(params.OTNIndexes[p.Name()]).Config(), &oc.TerminalDevice_Channel{ + Description: ygot.String("OTN Logical Channel"), + Index: ygot.Uint32(params.OTNIndexes[p.Name()]), + LogicalChannelType: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN, + Assignment: map[uint32]*oc.TerminalDevice_Channel_Assignment{ + firstAssignmentIndex: { + Index: ygot.Uint32(firstAssignmentIndex), + OpticalChannel: ygot.String(params.OpticalChannelNames[p.Name()]), + Description: ygot.String("OTN to Optical Channel"), + Allocation: ygot.Float64(params.Allocation), + AssignmentType: oc.Assignment_AssignmentType_OPTICAL_CHANNEL, + }, + firstAssignmentIndex + 1: { + Index: ygot.Uint32(firstAssignmentIndex + 1), + LogicalChannel: ygot.Uint32(params.ETHIndexes[p.Name()]), + Description: ygot.String("OTN to ETH"), + Allocation: ygot.Float64(params.Allocation), + AssignmentType: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + }, + }) + } else { + gnmi.BatchReplace(batch, gnmi.OC().TerminalDevice().Channel(params.OTNIndexes[p.Name()]).Config(), &oc.TerminalDevice_Channel{ + Description: ygot.String("OTN Logical Channel"), + Index: ygot.Uint32(params.OTNIndexes[p.Name()]), + LogicalChannelType: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_OTN, + TribProtocol: params.TribProtocol, + AdminState: oc.TerminalDevice_AdminStateType_ENABLED, + Assignment: map[uint32]*oc.TerminalDevice_Channel_Assignment{ + firstAssignmentIndex: { + Index: ygot.Uint32(firstAssignmentIndex), + OpticalChannel: ygot.String(params.OpticalChannelNames[p.Name()]), + Description: ygot.String("OTN to Optical Channel"), + Allocation: ygot.Float64(params.Allocation), + AssignmentType: oc.Assignment_AssignmentType_OPTICAL_CHANNEL, + }, + }, + }) + } +} + +// updateETHChannelConfig updates the ETH channel config. +func updateETHChannelConfig(batch *gnmi.SetBatch, dut *ondatra.DUTDevice, p *ondatra.Port, params *ConfigParameters) { + var assignmentIndex uint32 + if deviations.EthChannelAssignmentCiscoNumbering(dut) { + assignmentIndex = 1 + } else { + assignmentIndex = 0 + } + var ingress *oc.TerminalDevice_Channel_Ingress + if !deviations.EthChannelIngressParametersUnsupported(dut) { + ingress = &oc.TerminalDevice_Channel_Ingress{ + Interface: ygot.String(p.Name()), + Transceiver: ygot.String(params.TransceiverNames[p.Name()]), + } + } + assignment := map[uint32]*oc.TerminalDevice_Channel_Assignment{ + assignmentIndex: { + Index: ygot.Uint32(assignmentIndex), + LogicalChannel: ygot.Uint32(params.OTNIndexes[p.Name()]), + Description: ygot.String("ETH to OTN"), + Allocation: ygot.Float64(params.Allocation), + AssignmentType: oc.Assignment_AssignmentType_LOGICAL_CHANNEL, + }, + } + if deviations.EthChannelAssignmentCiscoNumbering(dut) { + assignment[0].Index = ygot.Uint32(1) + } + channel := &oc.TerminalDevice_Channel{ + Description: ygot.String("ETH Logical Channel"), + Index: ygot.Uint32(params.ETHIndexes[p.Name()]), + LogicalChannelType: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_ETHERNET, + Ingress: ingress, + Assignment: assignment, + } + if !deviations.ChannelRateClassParametersUnsupported(dut) { + channel.RateClass = params.RateClass + } + if !deviations.OTNChannelTribUnsupported(dut) { + channel.TribProtocol = params.TribProtocol + channel.AdminState = oc.TerminalDevice_AdminStateType_ENABLED + } + gnmi.BatchReplace(batch, gnmi.OC().TerminalDevice().Channel(params.ETHIndexes[p.Name()]).Config(), channel) +} + +// ToggleInterfaceState toggles the interface with operational mode. +func ToggleInterfaceState(t *testing.T, p *ondatra.Port, params *ConfigParameters) { + i := &oc.Interface{ + Name: ygot.String(p.Name()), + Type: oc.IETFInterfaces_InterfaceType_ethernetCsmacd, + Enabled: ygot.Bool(params.Enabled), + } + if p.PMD() == ondatra.PMD800GBASEZR || p.PMD() == ondatra.PMD800GBASEZRP { + i.Ethernet = &oc.Interface_Ethernet{ + PortSpeed: params.PortSpeed, + DuplexMode: oc.Ethernet_DuplexMode_FULL, + } + } + gnmi.Replace(t, p.Device(), gnmi.OC().Interface(p.Name()).Config(), i) +} + +// InterfaceInitialize assigns OpMode with value received through operationalMode flag. func InterfaceInitialize(t *testing.T, dut *ondatra.DUTDevice, initialOperationalMode uint16) uint16 { once.Do(func() { t.Helper() @@ -75,7 +530,7 @@ func InterfaceInitialize(t *testing.T, dut *ondatra.DUTDevice, initialOperationa return InterfaceGetOpMode() } -// GetOpMode returns the opmode value after the Initialize function has been called +// InterfaceGetOpMode returns the opmode value after the Initialize function has been called func InterfaceGetOpMode() uint16 { return opmode } @@ -127,21 +582,42 @@ func ToggleInterface(t *testing.T, dut *ondatra.DUTDevice, intf string, isEnable gnmi.Replace(t, dut, gnmi.OC().Interface(intf).Config(), i) } +// OpticalChannelOpt is an option for ConfigOpticalChannel. +type OpticalChannelOpt func(*oc.Component_OpticalChannel) + +// WithLinePort sets the line-port for the optical channel if supported by the DUT. +func WithLinePort(dut *ondatra.DUTDevice, och string) OpticalChannelOpt { + return func(oc *oc.Component_OpticalChannel) { + if !deviations.LinePortUnsupported(dut) { + linePort := strings.ReplaceAll(och, "OpticalChannel", "Optics") + oc.LinePort = ygot.String(linePort) + } + } +} + // ConfigOpticalChannel configures the optical channel. -func ConfigOpticalChannel(t *testing.T, dut *ondatra.DUTDevice, och string, frequency uint64, targetOpticalPower float64, operationalMode uint16) { +func ConfigOpticalChannel(t *testing.T, dut *ondatra.DUTDevice, och string, frequency uint64, targetOpticalPower float64, operationalMode uint16, opts ...OpticalChannelOpt) { + opticalChannel := &oc.Component_OpticalChannel{ + OperationalMode: ygot.Uint16(operationalMode), + Frequency: ygot.Uint64(frequency), + TargetOutputPower: ygot.Float64(targetOpticalPower), + } + for _, opt := range opts { + opt(opticalChannel) + } + if opticalChannel.GetLinePort() != "" { + t.Logf("LinePort was configured for optical channel %s: %s", och, opticalChannel.GetLinePort()) + } gnmi.Replace(t, dut, gnmi.OC().Component(och).Config(), &oc.Component{ - Name: ygot.String(och), - OpticalChannel: &oc.Component_OpticalChannel{ - OperationalMode: ygot.Uint16(operationalMode), - Frequency: ygot.Uint64(frequency), - TargetOutputPower: ygot.Float64(targetOpticalPower), - }, + Name: ygot.String(och), + OpticalChannel: opticalChannel, }) } // ConfigOTNChannel configures the OTN channel. func ConfigOTNChannel(t *testing.T, dut *ondatra.DUTDevice, och string, otnIndex, ethIndex uint32) { t.Helper() + t.Logf(" otnIndex:%v, ethIndex: %v", otnIndex, ethIndex) if deviations.OTNChannelTribUnsupported(dut) { gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex).Config(), &oc.TerminalDevice_Channel{ Description: ygot.String("OTN Logical Channel"), @@ -158,13 +634,6 @@ func ConfigOTNChannel(t *testing.T, dut *ondatra.DUTDevice, och string, otnIndex }, }) } else { - t.Logf(" otnIndex:%v, ethIndex: %v", otnIndex, ethIndex) - gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(ethIndex).Config(), &oc.TerminalDevice_Channel{ - Description: ygot.String("ETH Logical Channel"), - Index: ygot.Uint32(ethIndex), - LogicalChannelType: oc.TransportTypes_LOGICAL_ELEMENT_PROTOCOL_TYPE_PROT_ETHERNET, - TribProtocol: oc.TransportTypes_TRIBUTARY_PROTOCOL_TYPE_PROT_400GE, - }) gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(otnIndex).Config(), &oc.TerminalDevice_Channel{ Description: ygot.String("OTN Logical Channel"), Index: ygot.Uint32(otnIndex), @@ -220,3 +689,39 @@ func ConfigETHChannel(t *testing.T, dut *ondatra.DUTDevice, interfaceName, trans } gnmi.Replace(t, dut, gnmi.OC().TerminalDevice().Channel(ethIndex).Config(), channel) } + +// SetupAggregateAtomically sets up the aggregate interface atomically. +func SetupAggregateAtomically(t *testing.T, dut *ondatra.DUTDevice, aggID string, dutAggPorts []*ondatra.Port) { + d := &oc.Root{} + + d.GetOrCreateLacp().GetOrCreateInterface(aggID) + + agg := d.GetOrCreateInterface(aggID) + agg.GetOrCreateAggregation().LagType = oc.IfAggregate_AggregationType_LACP + agg.Type = ieee8023adLag + + for _, port := range dutAggPorts { + i := d.GetOrCreateInterface(port.Name()) + i.GetOrCreateEthernet().AggregateId = ygot.String(aggID) + i.Type = ethernetCsmacd + + if deviations.InterfaceEnabled(dut) { + i.Enabled = ygot.Bool(true) + } + } + + p := gnmi.OC() + fptest.LogQuery(t, fmt.Sprintf("%s to Update()", dut), p.Config(), d) + gnmi.Update(t, dut, p.Config(), d) +} + +// DeleteAggregate deletes the aggregate interface. +func DeleteAggregate(t *testing.T, dut *ondatra.DUTDevice, aggID string, dutAggPorts []*ondatra.Port) { + // Clear the aggregate minlink. + gnmi.Delete(t, dut, gnmi.OC().Interface(aggID).Aggregation().MinLinks().Config()) + + // Clear the members of the aggregate. + for _, port := range dutAggPorts { + gnmi.Delete(t, dut, gnmi.OC().Interface(port.Name()).Ethernet().AggregateId().Config()) + } +} diff --git a/internal/cfgplugins/mpls.go b/internal/cfgplugins/mpls.go new file mode 100644 index 00000000000..3c599d82589 --- /dev/null +++ b/internal/cfgplugins/mpls.go @@ -0,0 +1,271 @@ +// Copyright 2023 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 cfgplugins + +import ( + "fmt" + "testing" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/fptest" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" +) + +// MPLSStaticLSP configures static MPLS label binding using OC on device. +func MPLSStaticLSP(t *testing.T, batch *gnmi.SetBatch, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, nextHopIP string, intfName string, protocolType string) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + if intfName != "" { + cliConfig = fmt.Sprintf(` + mpls ip + mpls static top-label %v %s %s pop payload-type %s + `, incomingLabel, intfName, nextHopIP, protocolType) + } else { + cliConfig = fmt.Sprintf(` + mpls ip + mpls static top-label %v %s pop payload-type %s + `, incomingLabel, nextHopIP, protocolType) + } + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspOCUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + fptest.ConfigureDefaultNetworkInstance(t, dut) + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + staticMplsCfg := mplsCfg.GetOrCreateLsps().GetOrCreateStaticLsp(lspName) + staticMplsCfg.GetOrCreateEgress().SetIncomingLabel(oc.UnionUint32(incomingLabel)) + staticMplsCfg.GetOrCreateEgress().SetNextHop(nextHopIP) + staticMplsCfg.GetOrCreateEgress().SetPushLabel(oc.Egress_PushLabel_IMPLICIT_NULL) + + gnmi.BatchReplace(batch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +// NewStaticMplsLspPopLabel configures static MPLS label binding (LBL1) using CLI with deviation, if OC is unsupported on the device. +func NewStaticMplsLspPopLabel(t *testing.T, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, intfName string, nextHopIP string, protocolType string) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + if intfName != "" { + cliConfig = fmt.Sprintf(` + mpls ip + mpls static top-label %v %s %s pop payload-type %s + `, incomingLabel, intfName, nextHopIP, protocolType) + } else { + cliConfig = fmt.Sprintf(` + mpls ip + mpls static top-label %v %s pop payload-type %s + `, incomingLabel, nextHopIP, protocolType) + } + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + fptest.ConfigureDefaultNetworkInstance(t, dut) + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + staticMplsCfg := mplsCfg.GetOrCreateLsps().GetOrCreateStaticLsp(lspName) + staticMplsCfg.GetOrCreateEgress().SetIncomingLabel(oc.UnionUint32(incomingLabel)) + staticMplsCfg.GetOrCreateEgress().SetNextHop(nextHopIP) + staticMplsCfg.GetOrCreateEgress().SetPushLabel(oc.Egress_PushLabel_IMPLICIT_NULL) + + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +// RemoveStaticMplsLspPopLabel removes static MPLS POP label binding using CLI with deviation, if OC is unsupported on the device. +func RemoveStaticMplsLspPopLabel(t *testing.T, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, intfName string, nextHopIP string, protocolType string) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + if intfName != "" { + cliConfig = fmt.Sprintf(` + no mpls static top-label %v %s %s pop payload-type %s + `, incomingLabel, intfName, nextHopIP, protocolType) + } else { + cliConfig = fmt.Sprintf(` + no mpls static top-label %v %s pop payload-type %s + `, incomingLabel, nextHopIP, protocolType) + } + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + fptest.ConfigureDefaultNetworkInstance(t, dut) + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + mplsCfg.GetOrCreateLsps().DeleteStaticLsp(lspName) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +// NewStaticMplsLspSwapLabel configures a static MPLS LSP and swaps label. +func NewStaticMplsLspSwapLabel(t *testing.T, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, nextHopIP string, mplsSwapLabelTo uint32, lspNextHopIndex uint32) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + + cliConfig = fmt.Sprintf(` + mpls ip + mpls static top-label %v %s swap-label %v + `, incomingLabel, nextHopIP, mplsSwapLabelTo) + + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + // ConfigureDefaultNetworkInstance configures the default network instance name and type. + fptest.ConfigureDefaultNetworkInstance(t, dut) + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + staticMplsCfg := mplsCfg.GetOrCreateLsps().GetOrCreateStaticLsp(lspName) + staticMplsCfg.GetOrCreateEgress().SetIncomingLabel(oc.UnionUint32(incomingLabel)) + staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetIpAddress(nextHopIP) + staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetPushLabel(oc.UnionUint32(mplsSwapLabelTo)) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +// RemoveStaticMplsLspSwapLabel removes a static MPLS LSP and swaps label. +func RemoveStaticMplsLspSwapLabel(t *testing.T, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, nextHopIP string, mplsSwapLabelTo uint32) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + + cliConfig = fmt.Sprintf(` + no mpls static top-label %v %s swap-label %v + `, incomingLabel, nextHopIP, mplsSwapLabelTo) + + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + fptest.ConfigureDefaultNetworkInstance(t, dut) + + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + mplsCfg.GetOrCreateLsps().DeleteStaticLsp(lspName) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +// NewStaticMplsLspPushLabel configures a static MPLS LSP. +func NewStaticMplsLspPushLabel(t *testing.T, dut *ondatra.DUTDevice, lspName string, intfName string, nextHopIP string, destIP string, mplsPushLabel uint32, lspNextHopIndex uint32, protocolType string) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + + cliConfig = fmt.Sprintf(` + mpls ip + nexthop-group TestGrp type mpls + entry 0 push label-stack %v nexthop %s + traffic-policies + traffic-policy MPLS_TRAFFIC_POLICY + match DA %s + destination prefix %s + actions + count + redirect next-hop group TestGrp + match ipv4-all-default ipv4 + match ipv6-all-default ipv6 + interface %s + traffic-policy input MPLS_TRAFFIC_POLICY + `, mplsPushLabel, nextHopIP, protocolType, destIP, intfName) + + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + // ConfigureDefaultNetworkInstance configures the default network instance name and type. + fptest.ConfigureDefaultNetworkInstance(t, dut) + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + staticMplsCfg := mplsCfg.GetOrCreateLsps().GetOrCreateStaticLsp(lspName) + staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetIpAddress(nextHopIP) + staticMplsCfg.GetOrCreateEgress().GetOrCreateLspNextHop(lspNextHopIndex).SetPushLabel(oc.UnionUint32(mplsPushLabel)) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +// RemoveStaticMplsLspPushLabel removes a static MPLS LSP. +func RemoveStaticMplsLspPushLabel(t *testing.T, dut *ondatra.DUTDevice, lspName string, intfName string) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + + cliConfig = fmt.Sprintf(` + interface %s + no traffic-policy input MPLS_TRAFFIC_POLICY + traffic-policies + no traffic-policy MPLS_TRAFFIC_POLICY + no nexthop-group TestGrp type mpls + `, intfName) + + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } + d := &oc.Root{} + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + mplsCfg.GetOrCreateLsps().DeleteStaticLsp(lspName) + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) +} + +func MPLSStaticLSPByPass(t *testing.T, batch *gnmi.SetBatch, dut *ondatra.DUTDevice, lspName string, incomingLabel uint32, nextHopIP string, protocolType string, byPass bool) { + if deviations.StaticMplsLspOCUnsupported(dut) { + cliConfig := "" + switch dut.Vendor() { + case ondatra.ARISTA: + cliConfig = fmt.Sprintf(` + mpls ip + mpls static top-label %v %s pop payload-type %s access-list bypass + `, incomingLabel, nextHopIP, protocolType) + helpers.GnmiCLIConfig(t, dut, cliConfig) + default: + t.Errorf("Deviation StaticMplsLspOCUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } else { + d := &oc.Root{} + fptest.ConfigureDefaultNetworkInstance(t, dut) + mplsCfg := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)).GetOrCreateMpls() + staticMplsCfg := mplsCfg.GetOrCreateLsps().GetOrCreateStaticLsp(lspName) + staticMplsCfg.GetOrCreateEgress().SetIncomingLabel(oc.UnionUint32(incomingLabel)) + staticMplsCfg.GetOrCreateEgress().SetNextHop(nextHopIP) + staticMplsCfg.GetOrCreateEgress().SetPushLabel(oc.Egress_PushLabel_IMPLICIT_NULL) + + gnmi.BatchReplace(batch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Mpls().Config(), mplsCfg) + } +} diff --git a/internal/cfgplugins/networkinstance.go b/internal/cfgplugins/networkinstance.go new file mode 100644 index 00000000000..4fe53d0594e --- /dev/null +++ b/internal/cfgplugins/networkinstance.go @@ -0,0 +1,168 @@ +// Copyright 2025 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 cfgplugins + +import ( + "fmt" + "testing" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +// Consts +const ( + IPv4 = "IPv4" + IPv6 = "IPv6" +) + +// Vrf struct +type VrfRule struct { + Index uint32 + IpType string + SourcePrefix string + PrefixLength uint8 + NetInstName string +} + +type NetworkInstanceParams struct { + Name string + Default bool +} + +func EnableDefaultNetworkInstanceBgp(t *testing.T, dut *ondatra.DUTDevice, dutAS uint32) { + d := gnmi.OC() + bgp := &oc.NetworkInstance_Protocol{ + Identifier: oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, + Name: ygot.String("BGP"), + Enabled: ygot.Bool(true), + Bgp: &oc.NetworkInstance_Protocol_Bgp{}, + } + + bgp.Bgp.Global = &oc.NetworkInstance_Protocol_Bgp_Global{ + As: ygot.Uint32(dutAS), + } + + gnmi.Replace(t, dut, d.NetworkInstance(deviations.DefaultNetworkInstance(dut)).Protocol(oc.PolicyTypes_INSTALL_PROTOCOL_TYPE_BGP, "BGP").Config(), bgp) +} + +// ConfigureNetworkInstance configures a new network instance on the DUT not using batch update +// TODO: To migrate to AssignInterfaceToNetworkInstance() which uses gnmi.BatchReplace +func ConfigureNetworkInstance(t *testing.T, dut *ondatra.DUTDevice, netInstName string, isDefault bool) *oc.NetworkInstance { + t.Logf("Creating new Network Instance: %s", netInstName) + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(netInstName) + + if isDefault { + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE + } else { + ni.Type = oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF + } + + return ni +} + +// UpdateNetworkInstanceOnDut updates the network instance on the DUT. Not using batch update +func UpdateNetworkInstanceOnDut(t *testing.T, dut *ondatra.DUTDevice, netInstName string, netInst *oc.NetworkInstance) { + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(netInstName).Config(), netInst) +} + +// ConfigureCustomNetworkInstance configures a default or non-default network instance name and type. +func NewNetworkInstance(t testing.TB, batch *gnmi.SetBatch, d *ondatra.DUTDevice, nip *NetworkInstanceParams) { + t.Helper() + //ni := nip.Name not required as for default we use deviations.DefaultNetworkInstance(d) + if nip.Default { + t.Logf("Creating default network instance: %s", deviations.DefaultNetworkInstance(d)) + defNiPath := gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(d)) + gnmi.BatchUpdate(batch, defNiPath.Config(), &oc.NetworkInstance{ + Name: ygot.String(deviations.DefaultNetworkInstance(d)), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE, + }) + } else { + t.Logf("Creating non-default network instance: %s", nip.Name) + ni := nip.Name + defNiPath := gnmi.OC().NetworkInstance(ni) + gnmi.BatchUpdate(batch, defNiPath.Config(), &oc.NetworkInstance{ + Name: ygot.String(ni), + Type: oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_L3VRF, + }) + } +} + +// AssignToNetworkInstance attaches a subinterface to a network instance. +// TODO: To migrate to AssignInterfaceToNetworkInstance() which uses gnmi.BatchReplace +func AssignToNetworkInstance(t testing.TB, d *ondatra.DUTDevice, i string, ni string, si uint32) { + t.Helper() + if ni == "" { + t.Fatalf("Network instance not provided for interface assignment") + } + netInst := &oc.NetworkInstance{Name: ygot.String(ni)} + intf := &oc.Interface{Name: ygot.String(i)} + netInstIntf, err := netInst.NewInterface(intf.GetName()) + if err != nil { + t.Errorf("Error fetching NewInterface for %s", intf.GetName()) + } + netInstIntf.Interface = ygot.String(intf.GetName()) + netInstIntf.Subinterface = ygot.Uint32(si) + if deviations.InterfaceRefInterfaceIDFormat(d) { + netInstIntf.Id = ygot.String(fmt.Sprintf("%s.%d", intf.GetName(), si)) + } else { + netInstIntf.Id = ygot.String(intf.GetName()) + } + if intf.GetOrCreateSubinterface(si) != nil { + gnmi.Update(t, d, gnmi.OC().NetworkInstance(ni).Config(), netInst) + } +} + +// AssignInterfaceToNetworkInstance attaches an interface to a network instance using batch update. +// This is required for vendors that do not support subinterfaces and only support interface to network instance assignment. +func AssignInterfaceToNetworkInstance(t testing.TB, batch *gnmi.SetBatch, d *ondatra.DUTDevice, i string, nip *NetworkInstanceParams, si uint32) { + var ni string + if nip.Default { + ni = deviations.DefaultNetworkInstance(d) + } else { + ni = nip.Name + } + if ni == "" { + t.Fatalf("Network instance not provided for interface assignment") + } + t.Logf("Assigning interface %s to network instance %s", i, ni) + netInst := &oc.NetworkInstance{Name: ygot.String(ni)} + intf := &oc.Interface{Name: ygot.String(i)} + netInstIntf, err := netInst.NewInterface(intf.GetName()) + if err != nil { + t.Errorf("Error fetching NewInterface for %s", intf.GetName()) + } + netInstIntf.Interface = ygot.String(intf.GetName()) + netInstIntf.Subinterface = ygot.Uint32(si) + switch d.Vendor() { + case ondatra.ARISTA: + netInstIntf.Id = ygot.String(intf.GetName()) + case ondatra.CISCO: + netInstIntf.Id = ygot.String(intf.GetName()) + case ondatra.NOKIA: + netInstIntf.Id = ygot.String(intf.GetName()) + case ondatra.JUNIPER: + netInstIntf.Id = ygot.String(intf.GetName() + "." + fmt.Sprint(si)) + default: + netInstIntf.Id = ygot.String(intf.GetName() + "." + fmt.Sprint(si)) + } + if intf.GetOrCreateSubinterface(si) != nil { + gnmi.BatchReplace(batch, gnmi.OC().NetworkInstance(ni).Config(), netInst) + } +} diff --git a/internal/cfgplugins/policyforwarding.go b/internal/cfgplugins/policyforwarding.go new file mode 100644 index 00000000000..626af5c9e07 --- /dev/null +++ b/internal/cfgplugins/policyforwarding.go @@ -0,0 +1,897 @@ +package cfgplugins + +import ( + "context" + "fmt" + "maps" + "slices" + "strings" + "testing" + + "github.com/openconfig/featureprofiles/internal/attrs" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +const ( + ethernetCsmacd = oc.IETFInterfaces_InterfaceType_ethernetCsmacd + ieee8023adLag = oc.IETFInterfaces_InterfaceType_ieee8023adLag +) + +// DecapPolicyParams defines parameters for the Decap MPLS in GRE policy and related MPLS configs. +type DecapPolicyParams struct { + PolicyID string + RuleSeqID uint32 + IPv4DestAddress string // For the match criteria in the decap rule + MPLSInterfaceID string // For MPLS global interface attributes (e.g., "Aggregate4") + StaticLSPNameIPv4 string + StaticLSPLabelIPv4 uint32 + StaticLSPNextHopIPv4 string + StaticLSPNameIPv6 string + StaticLSPLabelIPv6 uint32 + StaticLSPNextHopIPv6 string + StaticLSPNameMulticast string + StaticLSPLabelMulticast uint32 + StaticLSPNextHopMulticast string +} + +// OcPolicyForwardingParams holds parameters for generating the OC Policy Forwarding config. +type OcPolicyForwardingParams struct { + NetworkInstanceName string + InterfaceID string + AppliedPolicyName string + + // Policy Rule specific params + InnerDstIPv6 string + InnerDstIPv4 string + CloudV4NHG string + CloudV6NHG string + DecapPolicy DecapPolicyParams + GuePort uint32 + IpType string + Dynamic bool + TunnelIP string +} + +type PolicyForwardingRule struct { + Id uint32 + Name string + IpType string + SourceAddress string + DestinationAddress string + Dscp uint8 + Action *oc.NetworkInstance_PolicyForwarding_Policy_Rule_Action +} + +var ( + + // PolicyForwardingConfigv4Arista configuration for policy-forwarding for ipv4. + PolicyForwardingConfigv4Arista = ` +Traffic-policies + traffic-policy tp_cloud_id_3_20 + match bgpsetttlv4 ipv4 + ttl 1 + actions + redirect next-hop group 1V4_vlan_3_20 ttl 1 + set traffic class 3 + match icmpechov4 ipv4 + destination prefix 169.254.0.11/32 + protocol icmp type echo-reply code all + match ipv4-all-default ipv4 + actions + redirect next-hop group 1V4_vlan_3_20 + set traffic class 3 + match ipv6-all-default ipv6 + ! + ` + // PolicyForwardingConfigv6Arista configuration for policy-forwarding for ipv6. + PolicyForwardingConfigv6Arista = ` +Traffic-policies + traffic-policy tp_cloud_id_3_21 + match bgpsetttlv6 ipv6 + ttl 1 + ! + actions + count + redirect next-hop group 1V6_vlan_3_21 ttl 1 + set traffic class 3 + ! + match icmpv6 ipv6 + destination prefix 2600:2d00:0:1:8000:10:0:ca33/128 + protocol icmpv6 type echo-reply neighbor-advertisement code all + ! + + ! + match ipv4-all-default ipv4 + ! + match ipv6-all-default ipv6 + actions + count + redirect next-hop group 1V6_vlan_3_21 + set traffic class 3 + ! + ` + // PolicyForwardingConfigDualStackArista configuration for policy-forwarding for the dualstack. + PolicyForwardingConfigDualStackArista = ` + Traffic-policies + traffic-policy tp_cloud_id_3_22 + match bgpsetttlv6 ipv6 + ttl 1 + ! + actions + count + redirect next-hop group 1V6_vlan_3_22 ttl 1 + set traffic class 3 + ! + match icmpv6 ipv6 + destination prefix 2600:2d00:0:1:7000:10:0:ca33/128 + protocol icmpv6 type echo-reply neighbor-advertisement code all + ! + actions + count + ! + match bgpsetttlv4 ipv4 + ttl 1 + ! + actions + count + redirect next-hop group 1V4_vlan_3_22 ttl 1 + set traffic class 3 + ! + match icmpechov4 ipv4 + destination prefix 169.254.0.27/32 + protocol icmp type echo-reply code all + ! + actions + count + ! + match ipv4-all-default ipv4 + actions + count + redirect next-hop group 1V4_vlan_3_22 + set traffic class 3 + ! + match ipv6-all-default ipv6 + actions + count + redirect next-hop group 1V6_vlan_3_22 + set traffic class 3 + !` + // PolicyForwardingConfigMulticloudAristav4 configuration for policy-forwarding for multicloud ipv4. + PolicyForwardingConfigMulticloudAristav4 = ` + Traffic-policies + counter interface per-interface ingress + ! + traffic-policy tp_cloud_id_3_23 + match icmpechov4 ipv4 + destination prefix 169.254.0.33/32 + protocol icmp type echo-reply code all + ! + actions + count + ! + match bgpsetttlv4 ipv4 + ttl 1 + ! + actions + count + redirect next-hop group 1V4_vlan_3_23 ttl 1 + set traffic class 3 + ! + match ipv4-all-default ipv4 + actions + count + redirect next-hop group 1V4_vlan_3_23 + set traffic class 3 + ! + match ipv6-all-default ipv6 + ! +` + qosconfigArista = ` + qos map dscp 0 1 2 3 4 5 6 7 to traffic-class 0 + qos map dscp 8 9 10 11 12 13 14 15 to traffic-class 1 + qos map dscp 40 41 42 43 44 45 46 47 to traffic-class 4 + qos map dscp 48 49 50 51 52 53 54 55 to traffic-class 7 +! + policy-map type quality-of-service af3 + class class-default + set traffic-class 3 +! +` + + mplsLabelRangeArista = ` +mpls label range bgp-sr 16 0 +mpls label range dynamic 16 0 +mpls label range isis-sr 16 0 +mpls label range l2evpn 16 0 +mpls label range l2evpn ethernet-segment 16 0 +mpls label range ospf-sr 16 0 +mpls label range srlb 16 0 +mpls label range static 16 1048560 +! +` + decapGroupGREArista = ` +ip decap-group gre-decap + tunnel type gre + tunnel decap-ip 11.0.0.0/8 + tunnel overlay mpls qos map mpls-traffic-class to traffic-class +!` + + decapGroupGUEArista = ` +! +ip decap-group type udp destination port 6635 payload mpls +! +ip decap-group gre-decap + tunnel type udp + tunnel decap-ip 11.0.0.0/8 + tunnel overlay mpls qos map mpls-traffic-class to traffic-class +!` + + staticLSPArista = ` +mpls static top-label 99991 169.254.0.12 pop payload-type ipv4 access-list bypass +mpls static top-label 99992 2600:2d00:0:1:8000:10:0:ca32 pop payload-type ipv6 access-list bypass +mpls static top-label 99993 169.254.0.26 pop payload-type ipv4 access-list bypass +mpls static top-label 99994 2600:2d00:0:1:7000:10:0:ca32 pop payload-type ipv6 access-list bypass +` +) + +// InterfacelocalProxyConfig configures the interface local-proxy-arp. +func InterfacelocalProxyConfig(t *testing.T, dut *ondatra.DUTDevice, a *attrs.Attributes, aggID string) { + if deviations.LocalProxyOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + if a.IPv4 != "" { + helpers.GnmiCLIConfig(t, dut, fmt.Sprintf("interface %s.%d \n ip local-proxy-arp \n", aggID, a.Subinterface)) + } + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'local-proxy-arp'", dut.Vendor()) + } + } + +} + +// InterfaceQosClassificationConfig configures the interface qos classification. +func InterfaceQosClassificationConfig(t *testing.T, dut *ondatra.DUTDevice, a *attrs.Attributes, aggID string) { + if deviations.QosClassificationOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + helpers.GnmiCLIConfig(t, dut, fmt.Sprintf("interface %s.%d \n service-policy type qos input af3 \n", aggID, a.Subinterface)) + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'qos classification'", dut.Vendor()) + } + } +} + +// InterfacePolicyForwardingConfig configures the interface policy-forwarding config. +func InterfacePolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, a *attrs.Attributes, aggID string, pf *oc.NetworkInstance_PolicyForwarding, params OcPolicyForwardingParams) { + t.Helper() + + // Check if the DUT requires CLI-based configuration due to an OpenConfig deviation. + if deviations.InterfacePolicyForwardingOCUnsupported(dut) { + // If deviations exist, apply configuration using vendor-specific CLI commands. + switch dut.Vendor() { + case ondatra.ARISTA: // Currently supports Arista devices for CLI deviations. + // Format and apply the CLI command for traffic policy input. + helpers.GnmiCLIConfig(t, dut, fmt.Sprintf("interface %s.%d \n traffic-policy input tp_cloud_id_3_%d \n", aggID, a.Subinterface, a.Subinterface)) + default: + // Log a message if the vendor is not supported for this specific CLI deviation. + t.Logf("Unsupported vendor %s for native command support for deviation 'policy-forwarding config'", dut.Vendor()) + } + } else { + ApplyPolicyToInterfaceOC(t, pf, params.InterfaceID, params.AppliedPolicyName) + + } +} + +// MplsConfig configures the interface mpls. +func MplsConfig(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.MplsOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + helpers.GnmiCLIConfig(t, dut, "mpls ip") + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'mpls ip'", dut.Vendor()) + } + } +} + +// QosClassificationConfig configures the interface qos classification. +func QosClassificationConfig(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.QosClassificationOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + helpers.GnmiCLIConfig(t, dut, qosconfigArista) + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'qos classification'", dut.Vendor()) + } + } + +} + +// LabelRangeConfig configures the interface label range. +func LabelRangeConfig(t *testing.T, dut *ondatra.DUTDevice) { + if deviations.MplsLabelClassificationOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + helpers.GnmiCLIConfig(t, dut, mplsLabelRangeArista) + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'mpls label range'", dut.Vendor()) + } + } +} + +// PolicyForwardingConfig configures the interface policy-forwarding config. +func PolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, traffictype string, pf *oc.NetworkInstance_PolicyForwarding, params OcPolicyForwardingParams) { + t.Helper() + + // Check if the DUT requires CLI-based configuration due to an OpenConfig deviation. + if deviations.PolicyForwardingOCUnsupported(dut) { + // If deviations exist, apply configuration using vendor-specific CLI commands. + switch dut.Vendor() { + case ondatra.ARISTA: // Currently supports Arista devices for CLI deviations. + // Select and apply the appropriate CLI snippet based on 'traffictype'. + if traffictype == "v4" { + helpers.GnmiCLIConfig(t, dut, PolicyForwardingConfigv4Arista) + } else if traffictype == "v6" { + helpers.GnmiCLIConfig(t, dut, PolicyForwardingConfigv6Arista) + } else if traffictype == "dualstack" { + helpers.GnmiCLIConfig(t, dut, PolicyForwardingConfigDualStackArista) + } else if traffictype == "multicloudv4" { + helpers.GnmiCLIConfig(t, dut, PolicyForwardingConfigMulticloudAristav4) + } + default: + // Log a message if the vendor is not supported for this specific CLI deviation. + t.Logf("Unsupported vendor %s for native command support for deviation 'policy-forwarding config'", dut.Vendor()) + } + } else { + + RulesAndActions(params, pf) + + } +} + +// SetupPolicyForwardingInfraOC creates a new OpenConfig root object, the specified network instance, +// and the policy-forwarding container within it. +// It returns the root, the network instance, and the policy-forwarding container. +func SetupPolicyForwardingInfraOC(networkInstanceName string) (*oc.Root, *oc.NetworkInstance, *oc.NetworkInstance_PolicyForwarding) { + root := &oc.Root{} + ni := root.GetOrCreateNetworkInstance(networkInstanceName) + pf := ni.GetOrCreatePolicyForwarding() + return root, ni, pf +} + +// RulesAndActions defines forwarding policies, their rules, and associated next-hop groups +// within the provided policy-forwarding container, based on canonical OpenConfig modeling. +func RulesAndActions(params OcPolicyForwardingParams, pf *oc.NetworkInstance_PolicyForwarding) { + // --- Define the Main Policy (e.g., "customer1") and its Rules --- + pols := pf.GetOrCreatePolicy("customer1") + var ruleSeq uint32 = 1 + + // Rule 1: (derived from JSON "customer1_prefixv6_and_icmpv6_ns") + rule1 := pols.GetOrCreateRule(ruleSeq) + rule1.GetOrCreateIpv4().DestinationAddress = ygot.String(params.InnerDstIPv6) + rule1.GetOrCreateIpv6().GetOrCreateIcmpv6().Type = oc.Icmpv6Types_TYPE_NEIGHBOR_SOLICITATION + + // TODO: b/417988636 - Set the action to count + // rule1.GetOrCreateAction().Count = ygot.Bool(true) + ruleSeq++ + + // Rule 2: (derived from JSON "customer1_prefixv6_and_icmpv6_na") + rule2 := pols.GetOrCreateRule(ruleSeq) + rule2.GetOrCreateIpv4().DestinationAddress = ygot.String(params.InnerDstIPv6) + rule2.GetOrCreateIpv6().GetOrCreateIcmpv6().Type = oc.Icmpv6Types_TYPE_NEIGHBOR_ADVERTISEMENT + + // TODO: b/417988636 - Set the action to count + // rule2.GetOrCreateAction().Count = ygot.Bool(true) + ruleSeq++ + + // Rule 3: (derivGetOrCreateRules().ed from JSON "customer1_prefixv4_and_icmp") + rule3 := pols.GetOrCreateRule(ruleSeq) + rule3.GetOrCreateIpv4().DestinationAddress = ygot.String(params.InnerDstIPv4) + rule3.GetOrCreateIpv4().GetOrCreateIcmpv4().Type = oc.Icmpv4Types_TYPE_EXT_ECHO_REPLY + + // TODO: b/417988636 - Set the action to count + // rule3.GetOrCreateAction().Count = ygot.Bool(true) + ruleSeq++ + + // Rule 4: (derived from JSON "customer1_prefixv6_and_icmp") + rule4 := pols.GetOrCreateRule(ruleSeq) + rule4.GetOrCreateIpv6().DestinationAddress = ygot.String(params.InnerDstIPv6) + rule4.GetOrCreateIpv6().GetOrCreateIcmpv6().Type = oc.Icmpv6Types_TYPE_EXT_ECHO_REPLY + + // TODO: b/417988636 - Set the action to count + // rule4.GetOrCreateAction().Count = ygot.Bool(true) + ruleSeq++ + + // Rule 5: (derived from JSON "customer1_ttl_v4") + rule5 := pols.GetOrCreateRule(ruleSeq) + rule5.GetOrCreateIpv4().HopLimit = ygot.Uint8(1) + + // TODO: b/417988636 - Set the action to count + // rule5.GetOrCreateAction().Count = ygot.Bool(true) + // rule5.GetOrCreateAction().NextHopGroup = ygot.String(params.CloudV4NHG) + // rule5.GetOrCreateAction().SetTtl = ygot.Uint8(1) + ruleSeq++ + + // Rule 6: (derived from JSON "customer1_ttl_v6") + rule6 := pols.GetOrCreateRule(ruleSeq) + rule6.GetOrCreateIpv6().HopLimit = ygot.Uint8(1) + + // TODO: b/417988636 - Set the action to count + // rule6.GetOrCreateAction().Count = ygot.Bool(true) + // rule6.GetOrCreateAction().NextHopGroup = ygot.String(params.CloudV6NHG) + // rule6.GetOrCreateAction().SetHopLimit = ygot.Uint8(1) + ruleSeq++ + + // Rule 7: (derived from JSON "customer1_default_v4") + rule7 := pols.GetOrCreateRule(ruleSeq) + rule7.GetOrCreateIpv4().DestinationAddress = ygot.String(params.InnerDstIPv4) + // TODO: b/417988636 - Set the action to count + // rule7.GetOrCreateAction().Count = ygot.Bool(true) + // rule7.GetOrCreateAction().NextHopGroup = ygot.String(params.CloudV4NHG) + ruleSeq++ + + // Rule 8: (derived from JSON "customer1_default_v6") + rule8 := pols.GetOrCreateRule(ruleSeq) + rule8.GetOrCreateIpv6().DestinationAddress = ygot.String(params.InnerDstIPv6) + // TODO: sancheetaroy - Set the action to count + // rule8.GetOrCreateAction().Count = ygot.Bool(true) + // rule8.GetOrCreateAction().NextHopGroup = ygot.String(params.CloudV6NHG) + ruleSeq++ +} + +// DecapPolicyRulesandActionsGre configures the "decap MPLS in GRE" policy and related MPLS global and static LSP settings. +func DecapPolicyRulesandActionsGre(t *testing.T, pf *oc.NetworkInstance_PolicyForwarding, params OcPolicyForwardingParams) { + t.Helper() + + pols := pf.GetOrCreatePolicy("customer10") + var ruleSeq uint32 = 10 + var protocol uint8 = 4 + + rule10 := pols.GetOrCreateRule(ruleSeq) + rule10.GetOrCreateIpv4().DestinationAddress = ygot.String(params.InnerDstIPv4) + rule10.GetOrCreateIpv4().Protocol = oc.UnionUint8(protocol) + + rule10.GetOrCreateAction().DecapsulateGre = ygot.Bool(true) +} + +// DecapPolicyRulesandActionsGue configures the "decap MPLS in GUE" policy and related MPLS global and static LSP settings. +func DecapPolicyRulesandActionsGue(t *testing.T, pf *oc.NetworkInstance_PolicyForwarding, params OcPolicyForwardingParams) { + t.Helper() + + pols := pf.GetOrCreatePolicy("customer10") + var ruleSeq uint32 = 10 + var protocol uint8 = 4 + + rule10 := pols.GetOrCreateRule(ruleSeq) + rule10.GetOrCreateIpv4().DestinationAddress = ygot.String(params.InnerDstIPv4) + rule10.GetOrCreateIpv4().Protocol = oc.UnionUint8(protocol) + + rule10.GetOrCreateAction().DecapsulateGue = ygot.Bool(true) +} + +// MplsGlobalStaticLspAttributes configures the MPLS global static LSP attributes. +func MplsGlobalStaticLspAttributes(t *testing.T, ni *oc.NetworkInstance, params OcPolicyForwardingParams) { + t.Helper() + mplsCfgv4 := ni.GetOrCreateMpls() + staticMplsCfgv4 := mplsCfgv4.GetOrCreateLsps().GetOrCreateStaticLsp(params.DecapPolicy.StaticLSPNameIPv4) + egressv4 := staticMplsCfgv4.GetOrCreateEgress() + egressv4.IncomingLabel = oc.UnionUint32(params.DecapPolicy.StaticLSPLabelIPv4) + egressv4.NextHop = ygot.String(params.DecapPolicy.StaticLSPNextHopIPv4) + + mplsCfgv6 := ni.GetOrCreateMpls() + staticMplsCfgv6 := mplsCfgv6.GetOrCreateLsps().GetOrCreateStaticLsp(params.DecapPolicy.StaticLSPNameIPv6) + egressv6 := staticMplsCfgv6.GetOrCreateEgress() + egressv6.IncomingLabel = oc.UnionUint32(params.DecapPolicy.StaticLSPLabelIPv6) + egressv6.NextHop = ygot.String(params.DecapPolicy.StaticLSPNextHopIPv6) +} + +// ApplyPolicyToInterfaceOC configures the policy-forwarding interfaces section to apply the specified +// policy to the given interface ID. +func ApplyPolicyToInterfaceOC(t *testing.T, pf *oc.NetworkInstance_PolicyForwarding, interfaceID string, appliedPolicyName string) { + t.Helper() + iface := pf.GetOrCreateInterface(interfaceID) + iface.ApplyForwardingPolicy = ygot.String(appliedPolicyName) +} + +// PushPolicyForwardingConfig pushes the complete Policy Forwarding config. +func PushPolicyForwardingConfig(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance) { + t.Helper() + niPath := gnmi.OC().NetworkInstance(ni.GetName()).Config() + gnmi.Replace(t, dut, niPath, ni) +} + +// DecapGroupConfigGre configures the interface decap-group. +func DecapGroupConfigGre(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ocPFParams OcPolicyForwardingParams) { + if deviations.GueGreDecapUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + if ocPFParams.Dynamic { + t.Logf("Going into decap") + aristaGreDecapCLIConfig(t, dut, ocPFParams) + } else { + helpers.GnmiCLIConfig(t, dut, decapGroupGREArista) + } + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'decap-group config'", dut.Vendor()) + } + } else { + DecapPolicyRulesandActionsGre(t, pf, ocPFParams) + } +} + +// DecapGroupConfigGue configures the interface decap-group for GUE. +func DecapGroupConfigGue(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, ocPFParams OcPolicyForwardingParams) { + if deviations.GueGreDecapUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + if ocPFParams.Dynamic { + t.Logf("Going into decap") + aristaGueDecapCLIConfig(t, dut, ocPFParams) + } else { + helpers.GnmiCLIConfig(t, dut, decapGroupGUEArista) + } + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'decap-group config'", dut.Vendor()) + } + } else { + DecapPolicyRulesandActionsGue(t, pf, ocPFParams) + } +} + +// aristaGueDecapCLIConfig configures GUEDEcapConfig for Arista +func aristaGueDecapCLIConfig(t *testing.T, dut *ondatra.DUTDevice, params OcPolicyForwardingParams) { + + cliConfig := fmt.Sprintf(` + ip decap-group type udp destination port %v payload %s + tunnel type %s-over-udp udp destination port %v + ip decap-group %s + tunnel type UDP + tunnel decap-ip %s + tunnel decap-interface %s + `, params.GuePort, params.IpType, params.IpType, params.GuePort, params.AppliedPolicyName, params.TunnelIP, params.InterfaceID) + helpers.GnmiCLIConfig(t, dut, cliConfig) + +} + +// aristaGreDecapCLIConfig configures GREDEcapConfig for Arista +func aristaGreDecapCLIConfig(t *testing.T, dut *ondatra.DUTDevice, params OcPolicyForwardingParams) { + + cliConfig := fmt.Sprintf(` + ip decap-group %s + tunnel type gre + tunnel decap-ip %s + `, params.AppliedPolicyName, params.TunnelIP) + helpers.GnmiCLIConfig(t, dut, cliConfig) + +} + +// MPLSStaticLSPConfig configures the interface mpls static lsp. +func MPLSStaticLSPConfig(t *testing.T, dut *ondatra.DUTDevice, ni *oc.NetworkInstance, ocPFParams OcPolicyForwardingParams) { + if deviations.StaticMplsUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + helpers.GnmiCLIConfig(t, dut, staticLSPArista) + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'mpls static lsp'", dut.Vendor()) + } + } else { + MplsGlobalStaticLspAttributes(t, ni, ocPFParams) + } +} + +// Configure GRE decapsulated. Adding deviation when device doesn't support OC +func PolicyForwardingGreDecapsulation(t *testing.T, batch *gnmi.SetBatch, dut *ondatra.DUTDevice, decapIp string, policyName string, portName string, decapGrpName string) { + if deviations.GreDecapsulationOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + cliConfig := fmt.Sprintf(` + ip decap-group %s + tunnel type gre + tunnel decap-ip %s + `, decapGrpName, strings.Split(decapIp, "/")[0]) + helpers.GnmiCLIConfig(t, dut, cliConfig) + + default: + t.Errorf("Deviation GreDecapsulationUnsupported is not handled for the dut: %v", dut.Vendor()) + } + return + } else { + d := &oc.Root{} + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + ni1.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + npf := ni1.GetOrCreatePolicyForwarding() + np := npf.GetOrCreatePolicy(policyName) + np.PolicyId = ygot.String(policyName) + np.Type = oc.Policy_Type_PBR_POLICY + + npRule := np.GetOrCreateRule(10) + ip := npRule.GetOrCreateIpv4() + ip.DestinationAddressPrefixSet = ygot.String(decapIp) + npAction := npRule.GetOrCreateAction() + npAction.DecapsulateGre = ygot.Bool(true) + + port := dut.Port(t, portName) + ingressPort := port.Name() + t.Logf("Applying forwarding policy on interface %v ... ", ingressPort) + + intf := npf.GetOrCreateInterface(ingressPort) + intf.ApplyForwardingPolicy = ygot.String(policyName) + intf.GetOrCreateInterfaceRef().Interface = ygot.String(ingressPort) + + gnmi.BatchReplace(batch, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Config(), ni1) + } +} + +func ConfigureVrfSelectionPolicy(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, policyName string, vrfRules []VrfRule) { + t.Logf("Configuring VRF Selection Policy") + policy := pf.GetOrCreatePolicy(policyName) + policy.Type = oc.Policy_Type_VRF_SELECTION_POLICY + + for _, vrfRule := range vrfRules { + rule := policy.GetOrCreateRule(vrfRule.Index) + switch vrfRule.IpType { + case IPv4: + rule.GetOrCreateIpv4().SourceAddress = ygot.String(fmt.Sprintf("%s/%d", vrfRule.SourcePrefix, vrfRule.PrefixLength)) + case IPv6: + rule.GetOrCreateIpv6().SourceAddress = ygot.String(fmt.Sprintf("%s/%d", vrfRule.SourcePrefix, vrfRule.PrefixLength)) + default: + t.Fatalf("Unsupported IP type %s in vrf rule", vrfRule.IpType) + } + rule.GetOrCreateTransport() + ruleAction := rule.GetOrCreateAction() + ruleAction.SetNetworkInstance(vrfRule.NetInstName) + } +} + +func ApplyVrfSelectionPolicyToInterfaceOC(t *testing.T, pf *oc.NetworkInstance_PolicyForwarding, interfaceID string, appliedPolicyName string) { + t.Helper() + iface := pf.GetOrCreateInterface(interfaceID) + iface.ApplyVrfSelectionPolicy = ygot.String(appliedPolicyName) + iface.GetOrCreateInterfaceRef().Interface = ygot.String(interfaceID) + iface.GetOrCreateInterfaceRef().Subinterface = ygot.Uint32(0) +} + +func NewPolicyForwardingEncapGre(t *testing.T, dut *ondatra.DUTDevice, pf *oc.NetworkInstance_PolicyForwarding, policyName string, interfaceName string, targetName string, rules []PolicyForwardingRule) { + if deviations.PolicyForwardingGreEncapsulationOcUnsupported(dut) || deviations.PolicyForwardingToNextHopOcUnsupported(dut) { + t.Logf("Configuring pf through CLI") + newPolicyForwardingEncapGreFromCli(t, dut, policyName, interfaceName, targetName, rules) + } else { + t.Logf("Configuring pf through OC") + newPolicyForwardingEncapGreFromOC(t, pf, policyName, interfaceName, rules) + } +} + +func newPolicyForwardingEncapGreFromCli(t *testing.T, dut *ondatra.DUTDevice, policyName string, interfaceName string, targetName string, rules []PolicyForwardingRule) { + gnmiClient := dut.RawAPIs().GNMI(t) + tpConfig := getTrafficPolicyCliConfig(t, dut, policyName, interfaceName, targetName, rules) + t.Logf("Push the CLI Policy config:%s", dut.Vendor()) + gpbSetRequest := buildCliSetRequest(tpConfig) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Errorf("Failed to set policy forwarding from cli: %v", err) + } +} + +func newPolicyForwardingEncapGreFromOC(t *testing.T, pf *oc.NetworkInstance_PolicyForwarding, policyName string, interfaceName string, rules []PolicyForwardingRule) { + t.Helper() + policy := pf.GetOrCreatePolicy(policyName) + policy.Type = oc.Policy_Type_PBR_POLICY + for _, ruleConfig := range rules { + t.Logf("Processing rule %s", ruleConfig.Name) + rule := policy.GetOrCreateRule(ruleConfig.Id) + switch ruleConfig.IpType { + case IPv4: + ruleIpv4 := rule.GetOrCreateIpv4() + if ruleConfig.SourceAddress != "" { + ruleIpv4.SourceAddress = ygot.String(ruleConfig.SourceAddress) + } + if ruleConfig.DestinationAddress != "" { + ruleIpv4.DestinationAddress = ygot.String(ruleConfig.DestinationAddress) + } + if ruleConfig.Dscp != 0 { + ruleIpv4.Dscp = ygot.Uint8(ruleConfig.Dscp) + } + case IPv6: + ruleIpv6 := rule.GetOrCreateIpv6() + if ruleConfig.SourceAddress != "" { + ruleIpv6.SourceAddress = ygot.String(ruleConfig.SourceAddress) + } + if ruleConfig.DestinationAddress != "" { + ruleIpv6.DestinationAddress = ygot.String(ruleConfig.DestinationAddress) + } + if ruleConfig.Dscp != 0 { + ruleIpv6.Dscp = ygot.Uint8(ruleConfig.Dscp) + } + default: + t.Errorf("Unknown IP type %s in PolicyForwardingRule", ruleConfig.IpType) + return + } + if ruleConfig.Action != nil { + rule.Action = ruleConfig.Action + } + } +} + +func getTrafficPolicyCliConfig(t *testing.T, dut *ondatra.DUTDevice, policyName string, interfaceName string, targetName string, rules []PolicyForwardingRule) string { + switch dut.Vendor() { + case ondatra.ARISTA: + var matchRules, matchTarget string + var nhGroupTargets = make(map[string][]string) + var nhGroupsBySource = make(map[string]string) + var nhTTlBySource = make(map[string]uint8) + for _, ruleConfig := range rules { + t.Logf("Processing rule %s", ruleConfig.Name) + if ruleConfig.Action == nil || + ruleConfig.Name == "" { + t.Errorf("Invalid rule configuration: %v", ruleConfig) + return "" + } + if ruleConfig.DestinationAddress != "" { + matchTarget = fmt.Sprintf("destination prefix %s", ruleConfig.DestinationAddress) + } else if ruleConfig.SourceAddress != "" { + matchTarget = fmt.Sprintf("source prefix %s", ruleConfig.SourceAddress) + } else { + t.Errorf("Rule %s must have either SourceAddress or DestinationAddress defined", ruleConfig.Name) + return "" + } + switch ruleConfig.IpType { + case IPv4, IPv6: + matchRules += fmt.Sprintf(` + match %s %s + %s + actions + count`, ruleConfig.Name, strings.ToLower(ruleConfig.IpType), matchTarget) + if (*ruleConfig.Action).NextHop != nil { + matchRules += fmt.Sprintf(` + redirect next-hop %s + !`, *(*ruleConfig.Action).NextHop) + } else if (*ruleConfig.Action).EncapsulateGre != nil { + for _, targetKey := range slices.Sorted(maps.Keys((*ruleConfig.Action).EncapsulateGre.Target)) { + target := (*ruleConfig.Action).EncapsulateGre.Target[targetKey] + if target != nil { + if target.Source == nil || target.Destination == nil { + t.Errorf("Target in EncapsulateGre action must have Source and Destination defined") + return "" + } + if !slices.Contains(nhGroupTargets[*(target.Source)], *target.Destination) { + nhGroupTargets[*(target.Source)] = append(nhGroupTargets[*(target.Source)], *target.Destination) + } + if target.IpTtl != nil { + nhTTlBySource[*(target.Source)] = *target.IpTtl + } + } + } + index := 1 + nhGroups := "" + for source := range nhGroupTargets { + nhGroupName := fmt.Sprintf("%s_%d", targetName, index) + nhGroupsBySource[source] = nhGroupName + nhGroups += fmt.Sprintf("%s ", nhGroupName) + } + matchRules += fmt.Sprintf(` + redirect next-hop group %s + !`, nhGroups) + } + default: + t.Errorf("Unknown IP type %s in PolicyForwardingRule %s", ruleConfig.IpType, ruleConfig.Name) + return "" + } + } + + var ipv4GreNHs string + for src, destinations := range nhGroupTargets { + ipv4GreNHs += fmt.Sprintf(` + nexthop-group %s type gre`, nhGroupsBySource[src]) + if len(nhTTlBySource) > 0 && nhTTlBySource[src] > 0 { + ipv4GreNHs += fmt.Sprintf(` + ttl %d`, nhTTlBySource[src]) + } + ipv4GreNHs += fmt.Sprintf(` + tunnel-source %s`, src) + for index, dest := range destinations { + ipv4GreNHs += fmt.Sprintf(` + entry %d tunnel-destination %s`, index, dest) + } + } + + // Apply Policy on the interface + trafficPolicyConfig := fmt.Sprintf(` + traffic-policies + traffic-policy %s + %s + %s + ! + interface %s + traffic-policy input %s + `, policyName, matchRules, ipv4GreNHs, interfaceName, policyName) + return trafficPolicyConfig + default: + return "" + } +} + +// Configure GRE decapsulated. Adding deviation when device doesn't support OC +func NewConfigureGRETunnel(t *testing.T, dut *ondatra.DUTDevice, decapIp string, decapGrpName string) { + if deviations.GreDecapsulationOCUnsupported(dut) { + var decapIPAddr string + if strings.Contains(decapIp, "/") { + decapIPAddr = strings.Split(decapIp, "/")[0] + } else { + decapIPAddr = decapIp + } + switch dut.Vendor() { + case ondatra.ARISTA: + cliConfig := fmt.Sprintf(` + ip decap-group %s + tunnel type gre + tunnel decap-ip %s + `, decapGrpName, decapIPAddr) + helpers.GnmiCLIConfig(t, dut, cliConfig) + + default: + t.Errorf("Deviation GreDecapsulationUnsupported is not handled for the dut: %v", dut.Vendor()) + } + } else { + d := &oc.Root{} + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + ni1.SetType(oc.NetworkInstanceTypes_NETWORK_INSTANCE_TYPE_DEFAULT_INSTANCE) + npf := ni1.GetOrCreatePolicyForwarding() + np := npf.GetOrCreatePolicy("PBR-MAP") + np.PolicyId = ygot.String("PBR-MAP") + np.Type = oc.Policy_Type_PBR_POLICY + + npRule := np.GetOrCreateRule(10) + ip := npRule.GetOrCreateIpv4() + ip.DestinationAddressPrefixSet = ygot.String(decapIp) + npAction := npRule.GetOrCreateAction() + npAction.DecapsulateGre = ygot.Bool(true) + + port := dut.Port(t, "port1") + ingressPort := port.Name() + t.Logf("Applying forwarding policy on interface %v ... ", ingressPort) + + intf := npf.GetOrCreateInterface(ingressPort) + intf.ApplyForwardingPolicy = ygot.String("PBR-MAP") + intf.GetOrCreateInterfaceRef().Interface = ygot.String(ingressPort) + + gnmi.Update(t, dut, gnmi.OC().NetworkInstance(deviations.DefaultNetworkInstance(dut)).Config(), ni1) + } +} + +func ConfigureDutWithGueDecap(t *testing.T, dut *ondatra.DUTDevice, guePort int, ipType, tunIp, decapInt, policyName string, policyId int) { + t.Logf("Configure DUT with decapsulation UDP port %v", guePort) + if deviations.DecapsulateGueOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + cliConfig := fmt.Sprintf(` + ip decap-group type udp destination port %[1]d payload %[2]s + tunnel type %[2]s-over-udp udp destination port %[1]d + ip decap-group test + tunnel type UDP + tunnel decap-ip %[3]s + tunnel decap-interface %[4]s + `, guePort, ipType, tunIp, decapInt) + helpers.GnmiCLIConfig(t, dut, cliConfig) + + default: + t.Errorf("Deviation decapsulateGueOCUnsupported is not handled for the dut: %v", dut.Vendor()) + } + } else { + // TODO: As per the latest OpenConfig GNMI OC schema — the Encapsulation/Decapsulation sub-tree is not fully implemented, need to add OC commands once implemented. + d := &oc.Root{} + ni1 := d.GetOrCreateNetworkInstance(deviations.DefaultNetworkInstance(dut)) + npf := ni1.GetOrCreatePolicyForwarding() + np := npf.GetOrCreatePolicy(policyName) + np.PolicyId = ygot.String(policyName) + npRule := np.GetOrCreateRule(uint32(policyId)) + ip := npRule.GetOrCreateIpv4() + ip.DestinationAddressPrefixSet = ygot.String(tunIp) + ip.Protocol = oc.PacketMatchTypes_IP_PROTOCOL_IP_UDP + // transport := npRule.GetOrCreateTransport() + // transport.SetDestinationPort() + } +} diff --git a/internal/cfgplugins/qos.go b/internal/cfgplugins/qos.go new file mode 100644 index 00000000000..53674cc3041 --- /dev/null +++ b/internal/cfgplugins/qos.go @@ -0,0 +1,262 @@ +// Copyright 2023 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 cfgplugins + +import ( + "context" + "fmt" + "testing" + "time" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/qoscfg" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ondatra/netutil" + "github.com/openconfig/ygot/ygot" +) + +type QosClassifier struct { + Desc string + Name string + ClassType oc.E_Qos_Classifier_Type + TermID string + TargetGroup string + DscpSet []uint8 + RemarkDscp uint8 +} + +type SchedulerPolicy struct { + Desc string + Sequence uint32 + SetPriority bool + Priority oc.E_Scheduler_Priority + InputID string + InputType oc.E_Input_InputType + SetWeight bool + QueueName string + TargetGroup string +} + +type ForwardingGroup struct { + Desc string + QueueName string + TargetGroup string + Priority uint8 +} + +type QoSSchedulerInterface struct { + Desc string + QueueName string + Scheduler string +} + +func runCliCommand(t *testing.T, dut *ondatra.DUTDevice, cliCommand string) string { + cliClient := dut.RawAPIs().CLI(t) + output, err := cliClient.RunCommand(context.Background(), cliCommand) + if err != nil { + t.Fatalf("Failed to execute CLI command '%s': %v", cliCommand, err) + } + t.Logf("Received from cli: %s", output.Output()) + return output.Output() +} + +func NewQosInitialize(t *testing.T, dut *ondatra.DUTDevice) { + if dut.Vendor() == ondatra.ARISTA { + queues := netutil.CommonTrafficQueues(t, dut) + qosQNameSet := ` + configure terminal + ! + qos tx-queue %d name %s + ! + ` + qosMapTC := ` + configure terminal + ! + qos map traffic-class %d to tx-queue %d + ! + ` + + qosCfgTargetGroup := ` + configure terminal + ! + qos traffic-class %d name %s + ! + ` + + runCliCommand(t, dut, "show version") + + qList := []string{queues.BE1, queues.AF1, queues.AF2, queues.AF3, queues.AF4, queues.NC1} + for index, queue := range qList { + + runCliCommand(t, dut, fmt.Sprintf(qosQNameSet, index, queue)) + time.Sleep(time.Second) + runCliCommand(t, dut, fmt.Sprintf(qosMapTC, index, index)) + time.Sleep(time.Second) + runCliCommand(t, dut, fmt.Sprintf(qosCfgTargetGroup, index, fmt.Sprintf("target-group-%s", queue))) + time.Sleep(time.Second) + } + } +} + +func NewQoSClassifierConfiguration(t *testing.T, dut *ondatra.DUTDevice, q *oc.Qos, classifiers []QosClassifier) *oc.Qos { + + t.Logf("QoS classifiers config: %v", classifiers) + for _, tc := range classifiers { + t.Log(tc.Desc) + classifier := q.GetOrCreateClassifier(tc.Name) + classifier.SetName(tc.Name) + classifier.SetType(tc.ClassType) + + term, err := classifier.NewTerm(tc.TermID) + if err != nil { + t.Fatalf("Failed to create classifier.NewTerm(): %v", err) + } + + term.SetId(tc.TermID) + action := term.GetOrCreateActions() + if tc.TargetGroup != "" { + action.SetTargetGroup(tc.TargetGroup) + } + if len(tc.DscpSet) > 0 { + condition := term.GetOrCreateConditions() + condition.GetOrCreateIpv4().SetDscpSet(tc.DscpSet) + } + if tc.RemarkDscp != 0 { + action.GetOrCreateRemark().SetDscp = ygot.Uint8(tc.RemarkDscp) + } + } + return q +} + +func NewQoSSchedulerPolicy(t *testing.T, dut *ondatra.DUTDevice, q *oc.Qos, policies []SchedulerPolicy) *oc.Qos { + + t.Logf("QoS scheduler policy config: %v", policies) + schedulerPolicy := q.GetOrCreateSchedulerPolicy("scheduler") + schedulerPolicy.SetName("scheduler") + t.Logf("QoS scheduler policies config: %v", policies) + for _, tc := range policies { + s := schedulerPolicy.GetOrCreateScheduler(tc.Sequence) + s.SetSequence(tc.Sequence) + if tc.SetPriority { + s.SetPriority(tc.Priority) + } + input := s.GetOrCreateInput(tc.InputID) + input.SetId(tc.InputID) + input.SetInputType(tc.InputType) + input.SetQueue(tc.QueueName) + } + return q +} + +func NewQoSForwardingGroup(t *testing.T, dut *ondatra.DUTDevice, q *oc.Qos, forwardingGroups []ForwardingGroup) { + t.Logf("QoS forwarding groups config: %v", forwardingGroups) + for _, tc := range forwardingGroups { + qoscfg.SetForwardingGroup(t, dut, q, tc.TargetGroup, tc.QueueName) + } +} + +func NewQoSSchedulerInterface(t *testing.T, dut *ondatra.DUTDevice, q *oc.Qos, schedulerIntfs []QoSSchedulerInterface, schedulerPort string) *oc.Qos { + t.Logf("QoS output interface config: %v", schedulerIntfs) + schPort := dut.Port(t, schedulerPort) + for _, tc := range schedulerIntfs { + i := q.GetOrCreateInterface(schPort.Name()) + i.SetInterfaceId(schPort.Name()) + i.GetOrCreateInterfaceRef().Interface = ygot.String(schPort.Name()) + if deviations.InterfaceRefConfigUnsupported(dut) { + i.InterfaceRef = nil + } + output := i.GetOrCreateOutput() + schedulerPolicy := output.GetOrCreateSchedulerPolicy() + schedulerPolicy.SetName(tc.Scheduler) + queue := output.GetOrCreateQueue(tc.QueueName) + queue.SetName(tc.QueueName) + } + return q +} + +func NewQoSQueue(t *testing.T, dut *ondatra.DUTDevice, q *oc.Qos) { + queues := netutil.CommonTrafficQueues(t, dut) + + if deviations.QOSQueueRequiresID(dut) { + queueNames := []string{queues.NC1, queues.AF4, queues.AF3, queues.AF2, queues.AF1, queues.BE1} + for i, queue := range queueNames { + q1 := q.GetOrCreateQueue(queue) + q1.Name = ygot.String(queue) + queueid := len(queueNames) - i + q1.QueueId = ygot.Uint8(uint8(queueid)) + } + t.Logf("\nDUT %s %s %s requires QoS queue requires ID deviation \n\n", dut.Vendor(), dut.Model(), dut.Version()) + } +} + +func configureQosClassifierDscpRemarkFromCli(t *testing.T, dut *ondatra.DUTDevice, classifierName string, interfaceName string, ipv4DscpValues []uint8, ipv6DscpValues []uint8) { + gnmiClient := dut.RawAPIs().GNMI(t) + qosConfig := enableQosRemarkDscpCliConfig(dut, classifierName, interfaceName, ipv4DscpValues, ipv6DscpValues) + t.Logf("Push the CLI Qos config:%s", dut.Vendor()) + gpbSetRequest := buildCliSetRequest(qosConfig) + if _, err := gnmiClient.Set(context.Background(), gpbSetRequest); err != nil { + t.Errorf("Failed to set qos classifier from cli: %v", err) + } +} + +func ConfigureQosClassifierDscpRemark(t *testing.T, dut *ondatra.DUTDevice, qos *oc.Qos, classifierName string, interfaceName string, ipv4DscpValues []uint8, ipv6DscpValues []uint8) { + if deviations.QosRemarkOCUnsupported(dut) { + t.Logf("Configuring qos dscp remark through CLI") + configureQosClassifierDscpRemarkFromCli(t, dut, classifierName, interfaceName, ipv4DscpValues, ipv6DscpValues) + } else { + t.Logf("Configuring qos dscp remark through OC") + configureQosClassifierDscpRemarkFromOc(t, dut, qos, classifierName, ipv4DscpValues, ipv6DscpValues) + } +} + +func configureQosClassifierDscpRemarkFromOc(t *testing.T, dut *ondatra.DUTDevice, qos *oc.Qos, classifierName string, ipv4DscpValues []uint8, ipv6DscpValues []uint8) { + if qos == nil { + t.Fatal("Qos OC config must be defined") + } + var classifiers []QosClassifier + + for i, dscp := range ipv4DscpValues { + classifiers = append(classifiers, QosClassifier{ + Desc: fmt.Sprintf("IPv4 DSCP term %d", i+1), + Name: classifierName, + ClassType: oc.Qos_Classifier_Type_IPV4, + TermID: fmt.Sprintf("termV4-%d", i+1), + RemarkDscp: dscp, + }) + } + for i, dscp := range ipv6DscpValues { + classifiers = append(classifiers, QosClassifier{ + Desc: fmt.Sprintf("IPv6 DSCP term %d", i+1), + Name: classifierName, + ClassType: oc.Qos_Classifier_Type_IPV6, + TermID: fmt.Sprintf("termV6-%d", i+1), + RemarkDscp: dscp, + }) + } + + NewQoSClassifierConfiguration(t, dut, qos, classifiers) +} + +func enableQosRemarkDscpCliConfig(dut *ondatra.DUTDevice, classifierName string, interfaceName string, ipv4DscpValues []uint8, ipv6DscpValues []uint8) string { + switch dut.Vendor() { + case ondatra.ARISTA: + return ` + qos rewrite dscp + !` + default: + return "" + } +} diff --git a/internal/cfgplugins/sflow.go b/internal/cfgplugins/sflow.go index b008a931bca..69c1b44d860 100644 --- a/internal/cfgplugins/sflow.go +++ b/internal/cfgplugins/sflow.go @@ -36,7 +36,15 @@ func NewSFlowGlobalCfg(t *testing.T, batch *gnmi.SetBatch, newcfg *oc.Sampling_S if newcfg == nil { c.Enabled = ygot.Bool(true) c.SampleSize = ygot.Uint16(256) - c.IngressSamplingRate = ygot.Uint32(1000000) + // override ingress sampling rate if default value of 1000000 is not supported + if deviations.SflowIngressMinSamplingRate(d) != 0 { + switch d.Vendor() { + case ondatra.CISCO: + c.SetIngressSamplingRate(deviations.SflowIngressMinSamplingRate(d)) + } + } else { + c.SetIngressSamplingRate(1000000) + } // c.EgressSamplingRate = ygot.Uint32(1000000), TODO: verify if EgressSamplingRate is a required DUT feature c.Dscp = ygot.Uint8(8) c.GetOrCreateInterface(d.Port(t, "port1").Name()).Enabled = ygot.Bool(true) diff --git a/internal/cfgplugins/staticnexthop.go b/internal/cfgplugins/staticnexthop.go new file mode 100644 index 00000000000..cbf84b60214 --- /dev/null +++ b/internal/cfgplugins/staticnexthop.go @@ -0,0 +1,219 @@ +package cfgplugins + +import ( + "testing" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/helpers" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +var ( + nextHopGroupConfigIPV4Arista = ` + nexthop-group 1V4_vlan_3_20 type mpls-over-gre + tos 96 + ttl 64 + fec hierarchical + entry 0 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.208 + entry 1 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.209 + entry 2 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.210 + entry 3 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.211 + entry 4 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.212 + entry 5 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.213 + entry 6 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.215 + entry 7 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.216 + entry 8 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.217 + entry 9 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.218 + entry 10 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.219 + entry 11 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.220 + entry 12 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.221 + entry 13 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.222 + entry 14 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.223 + entry 15 push label-stack 116383 tunnel-destination 10.99.1.1 tunnel-source 10.235.143.224 +! +` + nextHopGroupConfigIPV6Arista = ` +nexthop-group 1V6_vlan_3_21 type mpls-over-gre +tos 96 +ttl 64 +fec hierarchical +entry 0 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.208 +entry 1 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.209 +entry 2 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.210 +entry 3 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.211 +entry 4 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.212 +entry 5 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.213 +entry 6 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.215 +entry 7 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.216 +entry 8 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.217 +entry 9 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.218 +entry 10 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.219 +entry 11 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.220 +entry 12 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.221 +entry 13 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.222 +entry 14 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.223 +entry 15 push label-stack 99999 tunnel-destination 10.99.1.2 tunnel-source 10.235.143.224 +! +` + nextHopGroupConfigDualStackIPV4Arista = ` +nexthop-group 1V4_vlan_3_22 type mpls-over-gre +tos 96 +ttl 64 +fec hierarchical +entry 0 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.208 +entry 1 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.209 +entry 2 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.210 +entry 3 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.211 +entry 4 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.212 +entry 5 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.213 +entry 6 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.215 +entry 7 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.216 +entry 8 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.217 +entry 9 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.218 +entry 10 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.219 +entry 11 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.220 +entry 12 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.221 +entry 13 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.222 +entry 14 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.223 +entry 15 push label-stack 362143 tunnel-destination 10.99.1.3 tunnel-source 10.235.143.224 +! + ` + nextHopGroupConfigDualStackIPV6Arista = ` + nexthop-group 1V6_vlan_3_22 type mpls-over-gre + tos 96 + ttl 64 + fec hierarchical + entry 0 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.208 + entry 1 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.209 + entry 2 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.210 + entry 3 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.211 + entry 4 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.212 + entry 5 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.213 + entry 6 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.215 + entry 7 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.216 + entry 8 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.217 + entry 9 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.218 + entry 10 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.219 + entry 11 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.220 + entry 12 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.221 + entry 13 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.222 + entry 14 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.223 + entry 15 push label-stack 899999 tunnel-destination 10.99.1.4 tunnel-source 10.235.143.224 +! + + ` + // nextHopGroupConfigMulticloudIPV4Arista : Arista specific configuration for next-hop-group for multicloud ipv4. + nextHopGroupConfigMulticloudIPV4Arista = ` + nexthop-group 1V4_vlan_3_23 type mpls-over-gre + tos 96 + ttl 64 + fec hierarchical + entry 0 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.208 + entry 1 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.209 + entry 2 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.210 + entry 3 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.211 + entry 4 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.212 + entry 5 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.213 + entry 6 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.215 + entry 7 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.216 + entry 8 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.217 + entry 9 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.218 + entry 10 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.219 + entry 11 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.220 + entry 12 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.221 + entry 13 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.222 + entry 14 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.223 + entry 15 push label-stack 965535 tunnel-destination 10.99.1.5 tunnel-source 10.235.143.224 +! + ` +) + +// NextHopGroupConfig configures the interface next-hop-group config. +func NextHopGroupConfig(t *testing.T, dut *ondatra.DUTDevice, traffictype string, ni *oc.NetworkInstance, params StaticNextHopGroupParams) { + if deviations.NextHopGroupOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + if traffictype == "v4" { + helpers.GnmiCLIConfig(t, dut, nextHopGroupConfigIPV4Arista) + } else if traffictype == "dualstack" { + helpers.GnmiCLIConfig(t, dut, nextHopGroupConfigDualStackIPV4Arista) + helpers.GnmiCLIConfig(t, dut, nextHopGroupConfigDualStackIPV6Arista) + } else if traffictype == "v6" { + helpers.GnmiCLIConfig(t, dut, nextHopGroupConfigIPV6Arista) + } else if traffictype == "multicloudv4" { + helpers.GnmiCLIConfig(t, dut, nextHopGroupConfigMulticloudIPV4Arista) + } + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'next-hop-group config'", dut.Vendor()) + } + } else { + configureNextHopGroups(t, ni, params) + } + +} + +// StaticNextHopGroupParams holds parameters for generating the OC Static Next Hop Group config. +type StaticNextHopGroupParams struct { + + // For the "MPLS_in_GRE_Encap" Next-Hop Group definition from JSON's "static" block + StaticNHGName string + NHIPAddr1 string + NHIPAddr2 string + OuterIpv4DstDef string + OuterIpv4Src1Def string + OuterIpv4Src2Def string + OuterDscpDef uint8 + OuterTTLDef uint8 + + // TODO: b/417988636 - Set the MplsLabel to the correct value. + +} + +// configureNextHopGroups configures the next-hop groups and their encapsulation headers. +func configureNextHopGroups(t *testing.T, ni *oc.NetworkInstance, params StaticNextHopGroupParams) { + t.Helper() + nhg := ni.GetOrCreateStatic().GetOrCreateNextHopGroup("MPLS_in_GRE_Encap") + nhg.GetOrCreateNextHop("Dest A-NH1").Index = ygot.String("Dest A-NH1") + nhg.GetOrCreateNextHop("Dest A-NH2").Index = ygot.String("Dest A-NH2") + ni.GetOrCreateStatic().GetOrCreateNextHop("Dest A-NH1").NextHop = oc.UnionString(params.NHIPAddr1) + ni.GetOrCreateStatic().GetOrCreateNextHop("Dest A-NH2").NextHop = oc.UnionString(params.NHIPAddr2) + + // Set the encap header for each next-hop + ueh1 := ni.GetOrCreateStatic().GetOrCreateNextHop("Dest A-NH1").GetOrCreateEncapHeader(1) + ueh1.GetOrCreateUdpV4().DstIp = ygot.String(params.OuterIpv4DstDef) + ueh1.GetOrCreateUdpV4().SrcIp = ygot.String(params.OuterIpv4Src1Def) + ueh1.GetOrCreateUdpV4().Dscp = ygot.Uint8(params.OuterDscpDef) + ueh1.GetOrCreateUdpV4().IpTtl = ygot.Uint8(params.OuterTTLDef) + + // TODO: b/417988636 - mpls to equal params.MplsLabel + // if params.MplsLabel != nil && len(params.MplsLabel) > 0 { + // ueh1.GetOrCreateMpls().Label = ygot.Uint32(100) + + ueh2 := ni.GetOrCreateStatic().GetOrCreateNextHop("Dest A-NH2").GetOrCreateEncapHeader(1) + ueh2.GetOrCreateUdpV4().DstIp = ygot.String(params.OuterIpv4DstDef) + ueh2.GetOrCreateUdpV4().SrcIp = ygot.String(params.OuterIpv4Src2Def) + ueh2.GetOrCreateUdpV4().Dscp = ygot.Uint8(params.OuterDscpDef) + ueh2.GetOrCreateUdpV4().IpTtl = ygot.Uint8(params.OuterTTLDef) + + // TODO: b/417988636 - mpls to equal params.MplsLabel + // if params.MplsLabel != nil && len(params.MplsLabel) > 0 { + // ueh2.GetOrCreateMpls().Label = ygot.Uint32(100) +} + +// NextHopGroupConfigForMulticloud configures the interface next-hop-group config for multicloud. +func NextHopGroupConfigForMulticloud(t *testing.T, dut *ondatra.DUTDevice, traffictype string, ni *oc.NetworkInstance, params StaticNextHopGroupParams) { + if deviations.NextHopGroupOCUnsupported(dut) { + switch dut.Vendor() { + case ondatra.ARISTA: + if traffictype == "multicloudv4" { + helpers.GnmiCLIConfig(t, dut, nextHopGroupConfigMulticloudIPV4Arista) + } + default: + t.Logf("Unsupported vendor %s for native command support for deviation 'next-hop-group config'", dut.Vendor()) + } + } else { + configureNextHopGroups(t, ni, params) + } +} diff --git a/internal/cfgplugins/system.go b/internal/cfgplugins/system.go new file mode 100644 index 00000000000..8838b35ab3d --- /dev/null +++ b/internal/cfgplugins/system.go @@ -0,0 +1,56 @@ +// Copyright 2025 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 cfgplugins is a collection of OpenConfig configuration libraries for system and network instance. +// +// Each plugin function has parameters for a gnmi Batch, values to use in the configuration +// and an ondatra.DUTDevice. Each function returns OpenConfig values. +// +// The configuration function will modify the batch which is passed in by reference. The caller may +// pass nil as the configuration values to use in which case the function will provide a set of +// default values. The ondatra.DUTDevice is used to determine any configuration deviations which +// may be necessary. +// +// The caller may choose to use the returned OC value to customize the values set by this +// function or for use in a non-batch use case. +package cfgplugins + +import ( + "testing" + + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/ondatra" + "github.com/openconfig/ondatra/gnmi" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +// CreateGNMIServer creates a gNMI server on the DUT on a given network-instance. +func CreateGNMIServer(batch *gnmi.SetBatch, t testing.TB, d *ondatra.DUTDevice, nip *NetworkInstanceParams) { + var ni string + if nip.Default { + ni = deviations.DefaultNetworkInstance(d) + } else { + ni = nip.Name + } + t.Logf("Creating gNMI server on network instance: %s", ni) + gnmiServerPath := gnmi.OC().System().GrpcServer(ni) + gnmi.BatchUpdate(batch, gnmiServerPath.Config(), &oc.System_GrpcServer{ + Name: ygot.String(ni), + Port: ygot.Uint16(9339), + Enable: ygot.Bool(true), + NetworkInstance: ygot.String(ni), + // Services: []oc.E_SystemGrpc_GRPC_SERVICE{oc.SystemGrpc_GRPC_SERVICE_GNMI}, + }) +} diff --git a/internal/components/components.go b/internal/components/components.go index e13fd064046..6edc6afc6f4 100644 --- a/internal/components/components.go +++ b/internal/components/components.go @@ -36,23 +36,35 @@ const ( standbyController = oc.Platform_ComponentRedundantRole_SECONDARY ) +// componentsCache stores fetched components per device. +// The key is the DUT name. This cache persists across different test functions +// within the same execution of `go test`. +var componentsCache = make(map[string][]*oc.Component) + +// fetchCachedComponents fetches all components from the DUT, using a package-level cache. +func fetchCachedComponents(t *testing.T, dut *ondatra.DUTDevice) []*oc.Component { + dutName := dut.Name() + if cached, ok := componentsCache[dutName]; ok { + t.Logf("Using cached components for DUT %s.", dutName) + return cached + } + t.Logf("Fetching all components for DUT %s.", dutName) + components := gnmi.GetAll[*oc.Component](t, dut, gnmi.OC().ComponentAny().State()) + componentsCache[dutName] = components + return components +} + // FindComponentsByType finds the list of components based on hardware type. func FindComponentsByType(t *testing.T, dut *ondatra.DUTDevice, cType oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT) []string { - components := gnmi.GetAll[*oc.Component](t, dut, gnmi.OC().ComponentAny().State()) + t.Helper() + components := fetchCachedComponents(t, dut) var s []string for _, c := range components { - if c.GetType() == nil { - t.Logf("Component %s type is missing from telemetry", c.GetName()) - continue - } - t.Logf("Component %s has type: %v", c.GetName(), c.GetType()) switch v := c.GetType().(type) { case oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT: if v == cType { s = append(s, c.GetName()) } - default: - t.Logf("Detected non-hardware component: (%T, %v)", c.GetType(), c.GetType()) } } return s @@ -60,21 +72,15 @@ func FindComponentsByType(t *testing.T, dut *ondatra.DUTDevice, cType oc.E_Platf // FindActiveComponentsByType finds the list of active components based on hardware type. func FindActiveComponentsByType(t *testing.T, dut *ondatra.DUTDevice, cType oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT) []string { + t.Helper() components := gnmi.GetAll[*oc.Component](t, dut, gnmi.OC().ComponentAny().State()) var s []string for _, c := range components { - if c.GetType() == nil { - t.Logf("Component %s type is missing from telemetry", c.GetName()) - continue - } - t.Logf("Component %s has type: %v", c.GetName(), c.GetType()) switch v := c.GetType().(type) { case oc.E_PlatformTypes_OPENCONFIG_HARDWARE_COMPONENT: if v == cType && c.OperStatus == oc.PlatformTypes_COMPONENT_OPER_STATUS_ACTIVE { s = append(s, c.GetName()) } - default: - t.Logf("Detected non-hardware component: (%T, %v)", c.GetType(), c.GetType()) } } return s @@ -82,20 +88,15 @@ func FindActiveComponentsByType(t *testing.T, dut *ondatra.DUTDevice, cType oc.E // FindSWComponentsByType finds the list of SW components based on a type. func FindSWComponentsByType(t *testing.T, dut *ondatra.DUTDevice, cType oc.E_PlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT) []string { - components := gnmi.GetAll[*oc.Component](t, dut, gnmi.OC().ComponentAny().State()) + t.Helper() + components := fetchCachedComponents(t, dut) var s []string for _, c := range components { - if c.GetType() == nil { - continue - } - t.Logf("Component %s has type: %v", c.GetName(), c.GetType()) switch v := c.GetType().(type) { case oc.E_PlatformTypes_OPENCONFIG_SOFTWARE_COMPONENT: if v == cType { s = append(s, c.GetName()) } - default: - // no-op for non-software components. } } return s @@ -133,6 +134,9 @@ func GetSubcomponentPath(name string, useNameOnly bool) *tpb.Path { // DUT. type Y struct { *ygnmi.Client + // typeCache caches the results of FindByType. + // Key: oc.Component_Type_Union, Value: []string (component names). + typeCache map[oc.Component_Type_Union][]string } // New creates a new ygnmi based helper from a *ondatra.DUTDevice. @@ -142,11 +146,21 @@ func New(t testing.TB, dut *ondatra.DUTDevice) Y { if err != nil { t.Fatalf("Could not create ygnmi.Client: %v", err) } - return Y{yc} + return Y{ + Client: yc, + typeCache: make(map[oc.Component_Type_Union][]string), + } } // FindByType finds the list of components based on component type. func (y Y) FindByType(ctx context.Context, want oc.Component_Type_Union) ([]string, error) { + if y.typeCache == nil { + y.typeCache = make(map[oc.Component_Type_Union][]string) + } + if cached, ok := y.typeCache[want]; ok { + return cached, nil + } + var names []string anyTypePath := ocpath.Root().ComponentAny().Type() @@ -168,6 +182,8 @@ func (y Y) FindByType(ctx context.Context, want oc.Component_Type_Union) ([]stri if len(names) < 1 { return nil, fmt.Errorf("none of the %d components match %v", len(values), want) } + + y.typeCache[want] = names return names, nil } diff --git a/internal/deviations/deviations.go b/internal/deviations/deviations.go index cf07c777a88..a69eca4d5eb 100644 --- a/internal/deviations/deviations.go +++ b/internal/deviations/deviations.go @@ -1165,11 +1165,6 @@ func EnableMultipathUnderAfiSafi(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetEnableMultipathUnderAfiSafi() } -// BgpAllowownasDiffDefaultValue permits a device to have a different default value for allow own as. -func BgpAllowownasDiffDefaultValue(dut *ondatra.DUTDevice) bool { - return lookupDUTDeviations(dut).GetBgpAllowownasDiffDefaultValue() -} - // OTNChannelAssignmentCiscoNumbering returns true if OTN channel assignment index starts from 1 instead of 0 func OTNChannelAssignmentCiscoNumbering(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetOtnChannelAssignmentCiscoNumbering() @@ -1343,6 +1338,101 @@ func ContainerzOCUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetContainerzOcUnsupported() } +// NextHopGroupOCUnsupported returns true if devices do not support next-hop-group config +func NextHopGroupOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetNextHopGroupConfigUnsupported() +} + +// QosShaperOCUnsupported returns true if qos shaper config is unsupported +func QosShaperOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetQosShaperConfigUnsupported() +} + +// EthernetOverMPLSogreOCUnsupported returns true if ethernet over mplsogre is unsupported +func EthernetOverMPLSogreOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetEthernetOverMplsogreUnsupported() +} + +// SflowOCUnsupported returns true if sflow is unsupported +func SflowOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSflowUnsupported() +} + +// MplsOCUnsupported returns true if mpls is unsupported +func MplsOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMplsUnsupported() +} + +// MacsecOCUnsupported returns true if macsec is unsupported +func MacsecOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMacsecUnsupported() +} + +// GueGreDecapOCUnsupported returns true if gue gre decap is unsupported +func GueGreDecapOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetGueGreDecapUnsupported() +} + +// MplsLabelClassificationOCUnsupported returns true if mpls label classification is unsupported +func MplsLabelClassificationOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMplsLabelClassificationUnsupported() +} + +// LocalProxyOCUnsupported returns true if local proxy is unsupported +func LocalProxyOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetLocalProxyUnsupported() +} + +// StaticMplsOCUnsupported returns true if static mpls is unsupported +func StaticMplsOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticMplsUnsupported() +} + +// QosClassificationOCUnsupported returns true if qos classification is unsupported +func QosClassificationOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetQosClassificationUnsupported() +} + +// PolicyForwardingOCUnsupported returns true if policy forwarding is unsupported +func PolicyForwardingOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPolicyForwardingUnsupported() +} + +// InterfacePolicyForwardingOCUnsupported returns true if interface policy forwarding is unsupported +func InterfacePolicyForwardingOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetInterfacePolicyForwardingUnsupported() +} + +// GueGreDecapUnsupported returns true if gue or gre decap is unsupported +func GueGreDecapUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetGueGreDecapUnsupported() +} + +// StaticMplsUnsupported returns true if static mpls is unsupported +func StaticMplsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticMplsUnsupported() +} + +// QosShaperStateOCUnsupported returns true if qos shaper state is unsupported +func QosShaperStateOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetQosShaperStateUnsupported() +} + +// CfmOCUnsupported returns true if CFM is unsupported +func CfmOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetCfmUnsupported() +} + +// LabelRangeOCUnsupported returns true if label range is unsupported +func LabelRangeOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetLabelRangeUnsupported() +} + +// StaticArpOCUnsupported returns true if static arp is unsupported +func StaticArpOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticArpUnsupported() +} + // BgpDistanceOcPathUnsupported returns true if BGP Distance OC telemetry path is not supported. func BgpDistanceOcPathUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpDistanceOcPathUnsupported() @@ -1368,7 +1458,172 @@ func PortSpeedUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetPortSpeedUnsupported() } +// PolicyForwardingToNextHopOcUnsupported returns true if policy forwarding to next hop is not supported on vendors +func PolicyForwardingToNextHopOcUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPolicyForwardingToNextHopOcUnsupported() +} + // BGPSetMedActionUnsupported returns true if there's no OC support for BGP set med action func BGPSetMedActionUnsupported(dut *ondatra.DUTDevice) bool { return lookupDUTDeviations(dut).GetBgpSetMedActionUnsupported() } + +// NumPhysyicalChannelsUnsupported returns true if there's no OC support for num-physical-channels +func NumPhysyicalChannelsUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetNumPhysicalChannelsUnsupported() +} + +// UseOldOCPathStaticLspNh returns true if the old OC path for static lsp next-hop is used +func UseOldOCPathStaticLspNh(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetUseOldOcPathStaticLspNh() +} + +// ConfigLeafCreateRequired returns true if leaf creation is required +func ConfigLeafCreateRequired(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetConfigLeafCreateRequired() +} + +// FrBreakoutFix returns true if the fix is needed +func FrBreakoutFix(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetFrBreakoutFix() +} + +// SkipInterfaceNameCheck returns if device requires skipping the interface name check. +func SkipInterfaceNameCheck(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipInterfaceNameCheck() +} + +// UnsupportedQoSOutputServicePolicy returns true if devices do not support qos output service-policy +func UnsupportedQoSOutputServicePolicy(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetUnsupportedQosOutputServicePolicy() +} + +// InterfaceOutputQueueNonStandardName returns true if devices have non-standard output queue names +func InterfaceOutputQueueNonStandardName(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetInterfaceOutputQueueNonStandardName() +} + +// MplsExpIngressClassifierOcUnsupported returns true if devices do not support classifying ingress packets based on the MPLS exp field +func MplsExpIngressClassifierOcUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMplsExpIngressClassifierOcUnsupported() +} + +// Devices that do not propagate IGP metric through redistribution +func DefaultNoIgpMetricPropagation(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetDefaultNoIgpMetricPropagation() +} + +// SkipBgpPeerGroupSendCommunityType return true if device needs to skip setting BGP send-community-type for peer group +func SkipBgpPeerGroupSendCommunityType(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipBgpPeerGroupSendCommunityType() +} + +// Devices that does have different AS path prepend order. +func BgpAsPathPrependOrderMismtach(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetBgpAsPathPrependOrderMismtach() +} + +// ExplicitSwapSrcDstMacNeededForLoopbackMode returns true if device needs to explicitly set swap-src-dst-mac for loopback mode +func ExplicitSwapSrcDstMacNeededForLoopbackMode(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetExplicitSwapSrcDstMacNeededForLoopbackMode() +} + +// LinkLocalInsteadOfNh returns true if device requires link-local instead of NH. +func LinkLocalInsteadOfNh(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetLinkLocalInsteadOfNh() +} + +// LowScaleAft returns if device requires link-local instead of NH. +func LowScaleAft(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetLowScaleAft() +} + +// MissingSystemDescriptionConfigPath returns true if device does not support config lldp system-description leaf +func MissingSystemDescriptionConfigPath(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetMissingSystemDescriptionConfigPath() +} + +// FEC uncorrectable errors accumulate over time and are not cleared unless the component is reset on target +func NonIntervalFecErrorCounter(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetNonIntervalFecErrorCounter() +} + +// NtpSourceAddressUnsupported returns true if NTP source address is not supported +func NtpSourceAddressUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetNtpSourceAddressUnsupported() +} + +// StaticMplsLspUnsupported returns true if static mpls lsp parameters are unsupported +func StaticMplsLspOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetStaticMplsLspOcUnsupported() +} + +// GreDecapsulationUnsupported returns true if decapsulation is not supported +func GreDecapsulationOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetGreDecapsulationOcUnsupported() +} + +// SRLB and SRGB configuration does not effective with OC config +func IsisSrgbSrlbUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisSrgbSrlbUnsupported() +} + +// Isis Prefix Segment config does not supported +func IsisSrPrefixSegmentConfigUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisSrPrefixSegmentConfigUnsupported() +} + +// Isis Node Segment Configuration do not supported +func IsisSrNodeSegmentConfigUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetIsisSrNodeSegmentConfigUnsupported() +} + +// SflowIngressMinSamplingRate returns the minimum sampling rate supported for sflow ingress on the device. +func SflowIngressMinSamplingRate(dut *ondatra.DUTDevice) uint32 { + return lookupDUTDeviations(dut).GetSflowIngressMinSamplingRate() +} + +// QosRemarkOCUnsupported returns true if Qos remark parameters are unsupported +func QosRemarkOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetQosRemarkOcUnsupported() +} + +// PolicyForwardingGREEncapsulationOCUnsupported returns true if policy forwarding GRE encapsulation is not supported on vendors +func PolicyForwardingGreEncapsulationOcUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPolicyForwardingGreEncapsulationOcUnsupported() +} + +// PolicyRuleCountersOCUnsupported returns true if policy forwarding Rule Counters is not supported on vendors +func PolicyRuleCountersOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPolicyRuleCountersOcUnsupported() +} + +// OTNToETHAssignment returns true if the device must have the OTN to ETH assignment. +func OTNToETHAssignment(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetOtnToEthAssignment() +} + +// NetworkInstanceImportExportPolicyOCUnsupported returns true if network instance import/export policy is not supported. +func NetworkInstanceImportExportPolicyOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetNetworkInstanceImportExportPolicyOcUnsuppored() +} + +// SkipOrigin returns true if the device does not support the 'origin' field in gNMI/gNOI RPC paths. +func SkipOrigin(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetSkipOrigin() +} + +// PredefinedMaxEcmpPaths returns true if max ecmp paths are predefined. +func PredefinedMaxEcmpPaths(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetPredefinedMaxEcmpPaths() +} + +// DecapGroupOCUnsupported returns true if decapsulation group is not supported +func DecapsulateGueOCUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetDecapsulateGueOcUnsupported() +} + +// LinePortUnsupported returns whether the DUT does not support line-port configuration on optical channel components. +func LinePortUnsupported(dut *ondatra.DUTDevice) bool { + return lookupDUTDeviations(dut).GetLinePortUnsupported() +} diff --git a/internal/deviations/deviations.textproto b/internal/deviations/deviations.textproto new file mode 100644 index 00000000000..5fddbe8d9f5 --- /dev/null +++ b/internal/deviations/deviations.textproto @@ -0,0 +1,1329 @@ +# proto-file: github.com/openconfig/featureprofiles/proto/deviations.proto +# proto-message: DeviationRegistry +# txtpbfmt: expand_all_children +# txtpbfmt: sort_repeated_fields_by_content +deviations: { + name: "route_policy_under_afi_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy" + } + } + } +} +deviations: { + name: "default_network_instance" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/config/name" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + deviation_values: { + oc_standard_value: { + string_val: "default" + } + vendor_specific_value: { + string_val: "DEFAULT" + } + } + } +} +deviations: { + name: "bgp_missing_oc_max_prefixes_configuration" + type: DEVIATION_TYPE_CLI + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/use-multiple-paths/ebgp/config/maximum-paths" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + clis: { + commands: "router.bgp.[0-9]+\n +neighbor.*maximum-routes.[0-9]+" + } + } +} +deviations: { + name: "routing_policy_chaining_unsupported" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "sflow_source_address_update_unsupported" + type: DEVIATION_TYPE_CLI + impacted_paths: { + ocpaths: { + name: "/sampling/sflow/collectors/collector/config/source-address" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + clis: { + commands: "sflow.vrf.*source-interface*.*" + } + } +} +deviations: { + name: "explicit_enable_bgp_on_default_vrf" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/config/as" + } + } + } +} +deviations: { + name: "isis_require_same_l1_metric_with_l2_metric" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/metric" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_default_policy_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/export-policy" + } + } + } +} +deviations: { + name: "route_policy_under_afi_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy" + } + } + } +} +deviations: { + name: "missing_isis_interface_afi_safi_enable" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/config/enabled" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/afi-safi/af/config/enabled" + } + } + } +} +deviations: { + name: "os_component_parent_is_chassis" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/components/component[name='EOS'/state/parent" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + deviation_values: { + vendor_specific_value: { + string_val: "Chassis" + } + } + } +} +deviations: { + name: "isis_instance_enabled_required" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/config/instance" + } + } + } +} +deviations: { + name: "missing_zr_optical_channel_tunable_parameters_telemetry" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/components/component/state/temperature/avg" + } + ocpaths: { + name: "/components/component/state/temperature/min" + } + ocpaths: { + name: "/components/component/state/temperature/max" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "network_instance_table_deletion_required" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/tables/table" + } + } + } +} +deviations: { + name: "bgp_global_extended_next_hop_encoding_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "isis_metric_style_telemetry_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/levels/level/state/metric-style" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "isis_interface_afi_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "require_routed_subinterface_0" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/interfaces/interface/subinterfaces/subinterface/ipv4/config/enabled" + } + } + } +} +deviations: { + name: "mismatched_hardware_resource_name_in_component" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/components/component/integrated-circuit/utilization/resources/resource/name" + } + } + platforms: { + issue_url: "" + platform: { + vendor: ARISTA + software_version_regex: "*.*" + } + deviation_values: { + oc_standard_value: { + string_val: "*.*" + } + vendor_specific_value: { + string_val: "*.*/-" + } + } + } +} +deviations: { + name: "isis_global_authentication_not_required" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/config/authentication-check" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + deviation_values: { + oc_standard_value: { + string_val: "true" + } + vendor_specific_value: { + string_val: "false" + } + } + } +} +deviations: { + name: "skip_optical_channel_output_power_interval" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/components/component/optical-channel/state/output-power/interval" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_global_extended_next_hop_encoding_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding" + } + } + } +} +deviations: { + name: "qos_buffer_allocation_config_required" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/qos/buffer-allocation-profiles/buffer-allocation-profile/queues/queue/config/static-shared-buffer-limit" + } + } + } +} +deviations: { + name: "interface_enabled" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/interfaces/interface/config/enabled" + } + } + } +} +deviations: { + name: "ssh_server_counters_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/system/ssh-server/state/counters/access-rejects" + } + ocpaths: { + name: "/system/ssh-server/state/counters/last-access-reject" + } + ocpaths: { + name: "/system/ssh-server/state/counters/access-accepts" + } + ocpaths: { + name: "/system/ssh-server/state/counters/last-access-accept" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "auto_negotiate_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/ethernet/config/auto-negotiate" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "missing_system_description_config_path" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/lldp/config/system-description" + } + ocpaths: { + name: "/lldp/state/system-description" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_explicit_prefix_limit_received" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/prefix-limit/config/max-prefixes" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv6-unicast/prefix-limit/config/max-prefixes" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/prefix-limit-received/config/max-prefixes" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv6-unicast/prefix-limit-received/config/max-prefixes" + } + } + } +} +deviations: { + name: "bgp_extended_community_index_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib/routes/route/state/ext-community-index" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "skip_non_bgp_route_export_check" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/sent" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "skip_prefix_set_mode" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/routing-policy/defined-sets/prefix-sets/prefix-set/config/mode" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "eth_channel_ingress_parameters_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/terminal-device/logical-channels/channel/ingress/config/transceiver" + } + ocpaths: { + name: "/terminal-device/logical-channels/channel/ingress/config/interface" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "load_interval_not_supported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/rates/config/load-interval" + } + ocpaths: { + name: "/interfaces/interface/rates/state/load-interval" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_default_policy_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/config/default-export-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/default-import-policy" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/apply-policy/state/default-export-policy" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "port_speed_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/ethernet/config/port-speed" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "isis_multi_topology_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/multi-topology/state/enabled" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/multi-topology/config afi-name" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/multi-topology/config/safi-name" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/multi-topology/state/afi-name" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/afi-safi/af/multi-topology/state/safi-name" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "install_position_and_install_component_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/components/component/state/install-position" + } + ocpaths: { + name: "/components/component/state/install-component" + } + } + platforms: { + issue_url: "" + platform: { + vendor: NOKIA + software_version_regex: "*.*" + } + } +} +deviations: { + name: "ipv6_router_advertisement_config_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/interval" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "ecn_profile_required_definition" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/qos/queue-management-profiles/queue-management-profile/wred/uniform/config/enable-ecn" + } + } + } +} +deviations: { + name: "bgp_aspathset_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-name" + } + ocpaths: { + name: "/routing-policy/defined-sets/bgp-defined-sets/as-path-sets/as-path-set/config/as-path-set-member" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "qos_voq_drop_counter_unsupported" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "ipv6_enable_for_gribi_nh_dmac" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/interfaces/interface/subinterfaces/subinterface/ipv6/config/enabled" + } + } + } +} +deviations: { + name: "bgp_set_med_v7_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/bgp-actions/config/set-med-action" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "community_invert_any_unsupported" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/bgp-conditions/match-community-set/config/match-set-options" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + deviation_values: { + oc_standard_value: { + string_val: "INVERT" + } + } + } +} +deviations: { + name: "gnmi_get_on_root_unsupported" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_prefixset_req_routepol_ref" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/routing-policy/policy-definitions/policy-definition/statements/statement/conditions/match-prefix-set/config/prefix-set" + } + } + } +} +deviations: { + name: "skip_bgp_send_community_type" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/config/send-community-type" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "skip_tcp_negotiated_mss_check" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/transport/state/tcp-mss" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_rib_oc_path_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv4-unicast/loc-rib" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/rib/afi-safis/afi-safi/ipv6-unicast/loc-rib" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "load_interval_not_supported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/rates/config/load-interval" + } + ocpaths: { + name: "/interfaces/interface/rates/state/load-interval" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + } +} +deviations: { + name: "interface_ref_interface_id_format" + type: DEVIATION_TYPE_VALUE + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/name" + } + } + platforms: { + issue_url: "" + platform: { + vendor: JUNIPER + software_version_regex: "*.*" + } + deviation_values: { + vendor_specific_value: { + string_val: "+.0" + } + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + deviation_values: { + vendor_specific_value: { + string_val: "+.0" + } + } + } +} +deviations: { + name: "qos_scheduler_ingress_policer_unsupported" + type: DEVIATION_TYPE_CLI + impacted_paths: { + ocpaths: { + name: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/one-rate-two-color/config/cir" + } + ocpaths: { + name: "/qos/scheduler-policies/scheduler-policy/schedulers/scheduler/one-rate-two-color/config/bc" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + clis: { + commands: "policy-map.*\n.class.*\n..police.[0-9]+.[kmg]bps" + } + } +} +deviations: { + name: "skip_isis_set_metric_style_type" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/isis-actions/config/set-metric-style-type" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "bgp_global_extended_next_hop_encoding_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/ipv4-unicast/config/extended-next-hop-encoding" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "connect_retry" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/timers/config/connect-retry" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "rib_wecmp" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/global/config/weighted-ecmp" + } + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/weighted-ecmp/config/load-balancing-weight" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "missing_isis_interface_afi_safi_enable" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/afi-safi/af/config/enabled" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "plq_generator_capabilities_max_mtu" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "enable_multipath_under_afi_safi" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/use-multiple-paths/ebgp/config/maximum-paths" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/global/afi-safis/afi-safi/use-multiple-paths/ebgp/config/maximum-paths" + } + } + } +} +deviations: { + name: "interface_counters_update_delayed" + type: DEVIATION_TYPE_UNSPECIFIED + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/state/counters" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "p4rt_modify_table_entry_unsupported" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "isis_dis_sysid_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/levels/level/adjacencies/adjacency/state/dis-system-id" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "skip_setting_statement_for_policy" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/routing-policy/policy-definitions/policy-definition/statements/statement/actions/config/policy-result" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "peer_group_def_ebgp_vrf_unsupported" + type: DEVIATION_TYPE_UNSPECIFIED + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/config/peer-group" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "ntp_source_address_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/system/ntp/servers/server/config/source-address" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "override_default_nh_scale" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "isis_interface_level_passive_unsupported" + type: DEVIATION_TYPE_PATH + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + additional_paths: { + ocpaths: { + name: "/interfaces/interface/subinterfaces/subinterface/vlan/match/single-tagged/config/vlan-id" + } + } + } +} +deviations: { + name: "gribi_mac_override_with_static_arp" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "ipv6_router_advertisement_interval_unsupported" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/interfaces/interface/subinterfaces/subinterface/ipv6/router-advertisement/config/interval" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "weighted_ecmp_fixed_packet_verification" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "prepolicy_received_routes" + type: DEVIATION_TYPE_PATH + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/bgp/neighbors/neighbor/afi-safis/afi-safi/state/prefixes/received-pre-policy" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "link_local_instead_of_nh" + type: DEVIATION_TYPE_UNSPECIFIED + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + } +} +deviations: { + name: "wecmp_auto_unsupported" + type: DEVIATION_TYPE_CLI + impacted_paths: { + ocpaths: { + name: "/network-instances/network-instance/protocols/protocol/isis/interfaces/interface/weighted-ecmp/config/load-balancing-weight" + } + } + platforms: { + issue_url: "" + platform: { + vendor: CISCO + software_version_regex: "*.*" + } + clis: { + commands: "router.isis.*\n.interface.*\n..address-family.(ipv4|ipv6).unicast\n...weight.[0-9]+\n..address-family.(ipv4|ipv6).unicast\n...weight [0-9]+" + } + } +} \ No newline at end of file diff --git a/internal/fptest/runtests.go b/internal/fptest/runtests.go index 0657d6c4674..90670eb219a 100644 --- a/internal/fptest/runtests.go +++ b/internal/fptest/runtests.go @@ -20,12 +20,14 @@ import ( "path/filepath" "strconv" "testing" + "unicode/utf8" log "github.com/golang/glog" "github.com/openconfig/featureprofiles/internal/metadata" "github.com/openconfig/featureprofiles/internal/pathutil" mpb "github.com/openconfig/featureprofiles/proto/metadata_go_proto" "github.com/openconfig/featureprofiles/topologies/binding" + gpb "github.com/openconfig/gnmi/proto/gnmi" "github.com/openconfig/ondatra" "github.com/openconfig/ygnmi/ygnmi" ) @@ -109,5 +111,15 @@ func datapointValidator(dp *ygnmi.DataPoint) error { } } + if dp.Value != nil { + typedVal := dp.Value + switch typedVal.Value.(type) { + case *gpb.TypedValue_StringVal: + if typedVal.GetStringVal() != "" && !utf8.ValidString(typedVal.GetStringVal()) { + return fmt.Errorf("datapoint string value %v is not a valid UTF-8 string", typedVal.GetStringVal()) + } + } + } + return nil } diff --git a/internal/fptest/runtests_tests.go b/internal/fptest/runtests_tests.go new file mode 100644 index 00000000000..93efc5923d5 --- /dev/null +++ b/internal/fptest/runtests_tests.go @@ -0,0 +1,88 @@ +package fptest + +import ( + "testing" + "time" + + gpb "github.com/openconfig/gnmi/proto/gnmi" + "github.com/openconfig/ygnmi/ygnmi" +) + +// TestDatapointValidator confirms behavior of datapointValidator. +func TestDatapointValidator(t *testing.T) { + tests := []struct { + name string + dp *ygnmi.DataPoint + wantErr bool + }{ + { + name: "valid timestamp", + dp: &ygnmi.DataPoint{ + Timestamp: time.Unix(1707215426, 123456789), + RecvTimestamp: time.Unix(1707215426, 123456790), + }, + wantErr: false, + }, + { + name: "receive timestamp before notification timestamp", + dp: &ygnmi.DataPoint{ + Timestamp: time.Unix(1707215426, 123456789), + RecvTimestamp: time.Unix(1707215426, 123456788), + }, + wantErr: true, + }, + { + name: "zero timestamp", + dp: &ygnmi.DataPoint{ + Timestamp: time.Time{}, + RecvTimestamp: time.Unix(1707215426, 123456790), + }, + wantErr: false, + }, + { + name: "valid UTF-8 string", + dp: &ygnmi.DataPoint{ + Timestamp: time.Unix(1707215426, 123456789), + RecvTimestamp: time.Unix(1707215426, 123456790), + Value: &gpb.TypedValue{Value: &gpb.TypedValue_StringVal{StringVal: "hello"}}, + }, + wantErr: false, + }, + { + name: "invalid UTF-8 string", + dp: &ygnmi.DataPoint{ + Timestamp: time.Unix(1707215426, 123456789), + RecvTimestamp: time.Unix(1707215426, 123456790), + Value: &gpb.TypedValue{Value: &gpb.TypedValue_StringVal{StringVal: "\xff\xfe\xfd"}}, + }, + wantErr: true, + }, + { + name: "empty string", + dp: &ygnmi.DataPoint{ + Timestamp: time.Unix(1707215426, 123456789), + RecvTimestamp: time.Unix(1707215426, 123456790), + Value: &gpb.TypedValue{Value: &gpb.TypedValue_StringVal{StringVal: ""}}, + }, + wantErr: false, + }, + { + name: "non-string value", + dp: &ygnmi.DataPoint{ + Timestamp: time.Unix(1707215426, 123456789), + RecvTimestamp: time.Unix(1707215426, 123456790), + Value: &gpb.TypedValue{Value: &gpb.TypedValue_IntVal{IntVal: 123}}, + }, + wantErr: false, + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + err := datapointValidator(tc.dp) + if (err != nil) != tc.wantErr { + t.Errorf("datapointValidator(%v) error = %v, wantErr %v", tc.dp, err, tc.wantErr) + } + }) + } +} diff --git a/feature/system/gnmi/benchmarking/internal/setup/setup.go b/internal/gnmi/setup/setup.go similarity index 100% rename from feature/system/gnmi/benchmarking/internal/setup/setup.go rename to internal/gnmi/setup/setup.go diff --git a/internal/gnoi/gnoi.go b/internal/gnoi/gnoi.go index 004d0334438..321637ea3ed 100644 --- a/internal/gnoi/gnoi.go +++ b/internal/gnoi/gnoi.go @@ -37,6 +37,7 @@ var ( OCAGENT: "Octa", P4RT: "P4Runtime", ROUTING: "Bgp-main", + ISIS: "Isis", }, ondatra.CISCO: { GRIBI: "emsd", @@ -71,6 +72,8 @@ const ( P4RT Daemon = "P4RT" // ROUTING is the routing daemon. ROUTING Daemon = "ROUTING" + // ISIS is the Isis daemon + ISIS Daemon = "ISIS" ) // signal type of termination request @@ -102,7 +105,10 @@ func KillProcess(t *testing.T, dut *ondatra.DUTDevice, daemon Daemon, signal spb Pid: uint32(pid), Restart: restart, } - gnoiClient.System().KillProcess(context.Background(), killProcessRequest) + _, err = gnoiClient.System().KillProcess(context.Background(), killProcessRequest) + if err != nil { + t.Logf("Error: %v in executing the kill process %v", err.Error(), daemonName) + } time.Sleep(120 * time.Second) diff --git a/internal/gribi/gribi.go b/internal/gribi/gribi.go index a4e84b36354..26ebe068f0b 100644 --- a/internal/gribi/gribi.go +++ b/internal/gribi/gribi.go @@ -268,7 +268,22 @@ func (c *Client) AddEntries(t testing.TB, entries []fluent.GRIBIEntry, expectedR t.Helper() c.fluentC.Modify().AddEntry(t, entries...) if err := c.AwaitTimeout(context.Background(), t, timeout); err != nil { - t.Fatalf("Error waiting to add NHG: %v", err) + t.Fatalf("Error waiting to add entries: %v", err) + } + for _, result := range expectedResults { + chk.HasResult(t, c.fluentC.Results(t), + result, + chk.IgnoreOperationID(), + ) + } +} + +// DeleteEntries deletes the input gRIBI entries and checks the success of the input OperationResults. +func (c *Client) DeleteEntries(t testing.TB, entries []fluent.GRIBIEntry, expectedResults []*client.OpResult) { + t.Helper() + c.fluentC.Modify().DeleteEntry(t, entries...) + if err := c.AwaitTimeout(context.Background(), t, timeout); err != nil { + t.Fatalf("Error waiting to delete entries: %v", err) } for _, result := range expectedResults { chk.HasResult(t, c.fluentC.Results(t), diff --git a/internal/helpers/helpers.go b/internal/helpers/helpers.go index a0a13183ca1..c519df114c5 100644 --- a/internal/helpers/helpers.go +++ b/internal/helpers/helpers.go @@ -154,3 +154,8 @@ func buildCliConfigRequest(config string) (*gpb.SetRequest, error) { } return gpbSetRequest, nil } + +// BuildCliConfigRequest Build config with Origin set to cli and Ascii encoded config. +func BuildCliConfigRequest(config string) (*gpb.SetRequest, error) { + return buildCliConfigRequest(config) +} diff --git a/internal/isissession/isissession.go b/internal/isissession/isissession.go index 3fc1dae70a8..5bfe7b14713 100644 --- a/internal/isissession/isissession.go +++ b/internal/isissession/isissession.go @@ -196,12 +196,28 @@ func New(t testing.TB) (*TestSession, error) { if err != nil { return nil, fmt.Errorf("unable to connect to gNMI on %v: %w", s.DUT, err) } + s.DUTConf = &oc.Root{} s.DUTPort1 = s.DUT.Port(t, "port1") s.DUTPort2 = s.DUT.Port(t, "port2") - s.DUTConf = &oc.Root{} // configure dut ports - DUTISISAttrs.ConfigOCInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort1.Name()), s.DUT) - DUTTrafficAttrs.ConfigOCInterface(s.DUTConf.GetOrCreateInterface(s.DUTPort2.Name()), s.DUT) + dutIntf1 := s.DUTConf.GetOrCreateInterface(s.DUTPort1.Name()) + dutIntf2 := s.DUTConf.GetOrCreateInterface(s.DUTPort2.Name()) + + dutPortsMap := map[*ondatra.Port]*oc.Interface{ + s.DUTPort1: dutIntf1, + s.DUTPort2: dutIntf2, + } + + for port, dutIntf := range dutPortsMap { + if deviations.FrBreakoutFix(s.DUT) && port.PMD() == ondatra.PMD100GBASEFR { + dutIntf.GetOrCreateEthernet().SetAutoNegotiate(false) + dutIntf.GetOrCreateEthernet().SetDuplexMode(oc.Ethernet_DuplexMode_FULL) + dutIntf.GetOrCreateEthernet().SetPortSpeed(oc.IfEthernet_ETHERNET_SPEED_SPEED_100GB) + } + } + + DUTISISAttrs.ConfigOCInterface(dutIntf1, s.DUT) + DUTTrafficAttrs.ConfigOCInterface(dutIntf2, s.DUT) // If there is no ate, any operation that requires the ATE will call // t.Fatal() instead. This is helpful for debugging the parts of the test diff --git a/internal/otg_helpers/otg_config_helpers/otgconfighelpers.go b/internal/otg_helpers/otg_config_helpers/otgconfighelpers.go index b82cadbbd32..cc173d8163f 100644 --- a/internal/otg_helpers/otg_config_helpers/otgconfighelpers.go +++ b/internal/otg_helpers/otg_config_helpers/otgconfighelpers.go @@ -1,4 +1,3 @@ -// Package otgconfighelpers provides helper functions to setup Protocol configurations on traffic generators. package otgconfighelpers import ( @@ -46,7 +45,7 @@ type Port struct { Interfaces []*InterfaceProperties MemberPorts []string LagID uint32 - Islag bool + IsLag bool } // InterfaceProperties is a struct to hold interface data. @@ -59,46 +58,46 @@ type InterfaceProperties struct { Vlan uint32 IPv4Len uint32 IPv6Len uint32 - Mac string + MAC string } -// ConfigureOtgNetworkInterface configures the network interface. -func ConfigureOtgNetworkInterface(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, a *Port) { - if a.Islag { - ConfigureOtgLag(t, top, ate, a) +// ConfigureNetworkInterface configures the network interface. +func ConfigureNetworkInterface(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, a *Port) { + t.Helper() + if a.IsLag { + ConfigureLag(t, top, ate, a) } else { top.Ports().Add().SetName(a.Name) } for _, intf := range a.Interfaces { - ConfigureOtgInterface(t, top, intf, a) + ConfigureInterface(top, intf, a) } } -// ConfigureOtgLag configures the aggregate port. -func ConfigureOtgLag(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, a *Port) { +// ConfigureLag configures the aggregate port. +func ConfigureLag(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice, a *Port) { + t.Helper() agg := top.Lags().Add().SetName(a.Name) agg.Protocol().Lacp().SetActorKey(1).SetActorSystemPriority(1).SetActorSystemId(a.AggMAC) for index, portName := range a.MemberPorts { p := ate.Port(t, portName) top.Ports().Add().SetName(p.ID()) - ConfigureOtgLagMemberPort(agg, p.ID(), a, index) + ConfigureLagMemberPort(agg, p.ID(), a, index) } } -// ConfigureOtgLagMemberPort configures the member port in the LAG. -func ConfigureOtgLagMemberPort(agg gosnappi.Lag, portID string, a *Port, index int) { +// ConfigureLagMemberPort configures the member port in the LAG. +func ConfigureLagMemberPort(agg gosnappi.Lag, portID string, a *Port, index int) { lagPort := agg.Ports().Add().SetPortName(portID) lagPort.Ethernet().SetMac(a.AggMAC).SetName(a.Name + "-" + portID) lagPort.Lacp().SetActorActivity("active").SetActorPortNumber(uint32(index) + 1).SetActorPortPriority(1).SetLacpduTimeout(0) } -// ConfigureOtgInterface configures the Ethernet for the LAG or subinterface. -func ConfigureOtgInterface(t *testing.T, top gosnappi.Config, intf *InterfaceProperties, a *Port) { - t.Helper() - +// ConfigureInterface configures the Ethernet for the LAG or subinterface. +func ConfigureInterface(top gosnappi.Config, intf *InterfaceProperties, a *Port) { dev := top.Devices().Add().SetName(intf.Name + ".Dev") - eth := dev.Ethernets().Add().SetName(intf.Name + ".Eth").SetMac(intf.Mac) - if a.Islag { + eth := dev.Ethernets().Add().SetName(intf.Name + ".Eth").SetMac(intf.MAC) + if a.IsLag { eth.Connection().SetLagName(a.Name) } else { eth.Connection().SetPortName(a.Name) diff --git a/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go b/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go index 23ffc578c2d..c9126c8fbbc 100644 --- a/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go +++ b/internal/otg_helpers/otg_config_helpers/otgflowhelpers.go @@ -1,3 +1,4 @@ +// Package otgconfighelpers contains helper functions to create OTG flows and protocol configurations. package otgconfighelpers import ( @@ -16,35 +17,40 @@ Flow is a struct to hold Flow parameters. TxNames and RxNames should be set to a valid OTG endpoint name. Creating basic IPv4 Flow. -top = gosnappi.NewConfig() - - FlowIPv4 = &Flow{ - TxNames: []string{"interface1"}, - RxNames: []string{"interface2"}, - FrameSize: 1500, - FlowName: "IPv4Flow", - EthFlow: &EthFlowParams{SrcMAC: "00:11:22:33:44:55", DstMAC: "00:11:22:33:44:66"}, - IPv4Flow: &IPv4FlowParams{IPv4Src: "192.0.2.1", IPv4Dst: "192.0.2.129", IPv4DstCount: 10}, - } - -FlowIPv4.CreateFlow(top) -FlowIPv4.AddEthHeader() -FlowIPv4.AddIPv4Header() + top = gosnappi.NewConfig() + FlowIPv4 = &Flow{ + TxNames: []string{"interface1"}, + RxNames: []string{"interface2"}, + FrameSize: 1500, + FlowName: "IPv4Flow", + EthFlow: &EthFlowParams{SrcMAC: "00:11:22:33:44:55", DstMAC: "00:11:22:33:44:66"}, + IPv4Flow: &IPv4FlowParams{IPv4Src: "12.1.1.1", IPv4Dst: "11.1.1.1", IPv4DstCount: 10}, + } + FlowIPv4.CreateFlow(top) + FlowIPv4.AddEthHeader() + FlowIPv4.AddIPv4Header() */ type Flow struct { - TxNames []string - RxNames []string - FrameSize uint32 - FlowName string - VLANFlow *VLANFlowParams - GREFlow *GREFlowParams - EthFlow *EthFlowParams - IPv4Flow *IPv4FlowParams - IPv6Flow *IPv6FlowParams - TCPFlow *TCPFlowParams - UDPFlow *UDPFlowParams - MPLSFlow *MPLSFlowParams - flow gosnappi.Flow + TxNames []string + RxNames []string + TxPort string + RxPorts []string + FrameSize uint32 + FlowName string + Flowrate float32 + PpsRate uint64 + PacketsToSend uint32 + IsTxRxPort bool + SizeWeightProfile *[]SizeWeightPair + VLANFlow *VLANFlowParams + GREFlow *GREFlowParams + EthFlow *EthFlowParams + IPv4Flow *IPv4FlowParams + IPv6Flow *IPv6FlowParams + TCPFlow *TCPFlowParams + UDPFlow *UDPFlowParams + MPLSFlow *MPLSFlowParams + flow gosnappi.Flow } // GREFlowParams is a struct to hold Ethernet traffic parameters. @@ -54,7 +60,8 @@ type GREFlowParams struct { // VLANFlowParams is a struct to hold VLAN traffic parameters. type VLANFlowParams struct { - VLANId uint32 + VLANId uint32 + VLANCount uint32 } // EthFlowParams is a struct to hold Ethernet traffic parameters. @@ -62,24 +69,33 @@ type EthFlowParams struct { SrcMAC string DstMAC string SrcMACCount uint32 + DstMACCount uint32 } // IPv4FlowParams is a struct to hold IPv4 traffic parameters. type IPv4FlowParams struct { - IPv4Src string - IPv4Dst string - IPv4SrcCount uint32 - IPv4DstCount uint32 - TTL uint32 + IPv4Src string + IPv4Dst string + IPv4SrcCount uint32 + IPv4DstCount uint32 + TTL uint32 + TOS uint32 + DSCP uint32 + TOSCount uint32 + DSCPCount uint32 + RawPriority uint32 + RawPriorityCount uint32 } // IPv6FlowParams is a struct to hold IPv6 traffic parameters. type IPv6FlowParams struct { - IPv6Src string - IPv6Dst string - IPv6SrcCount uint32 - IPv6DstCount uint32 - HopLimit uint32 + IPv6Src string + IPv6Dst string + IPv6SrcCount uint32 + IPv6DstCount uint32 + HopLimit uint32 + TrafficClass uint32 + TrafficClassCount uint32 } // TCPFlowParams is a struct to hold TCP traffic parameters. @@ -100,25 +116,65 @@ type UDPFlowParams struct { // MPLSFlowParams is a struct to hold MPLS traffic parameters. type MPLSFlowParams struct { - MPLSLabel uint32 - MPLSExp uint32 + MPLSLabel uint32 + MPLSExp uint32 + MPLSLabelCount uint32 + MPLSExpCount uint32 +} + +// SizeWeightPair represents a custom Size profile for a traffic flow. +type SizeWeightPair struct { + Size uint32 + Weight float32 } // CreateFlow defines Tx and Rx end points for traffic flow. func (f *Flow) CreateFlow(top gosnappi.Config) { f.flow = top.Flows().Add().SetName(f.FlowName) f.flow.Metrics().SetEnable(true) - f.flow.TxRx().Device(). - SetTxNames(f.TxNames). - SetRxNames(f.RxNames) - f.flow.Size().SetFixed(f.FrameSize) + if f.IsTxRxPort { + f.flow.TxRx().Port(). + SetTxName(f.TxPort). + SetRxNames(f.RxPorts) + } else { + f.flow.TxRx().Device(). + SetTxNames(f.TxNames). + SetRxNames(f.RxNames) + } + if f.FrameSize != 0 { + f.flow.Size().SetFixed(f.FrameSize) + } + if f.Flowrate != 0 { + f.flow.Rate().SetPercentage(f.Flowrate) + } + if f.PpsRate != 0 { + f.flow.Rate().SetPps(f.PpsRate) + } + if f.PacketsToSend != 0 { + f.flow.Duration().FixedPackets().SetPackets(f.PacketsToSend) + } + if f.SizeWeightProfile != nil { + for _, sizeWeight := range *f.SizeWeightProfile { + f.flow.Size().WeightPairs().Custom().Add().SetSize(sizeWeight.Size).SetWeight(sizeWeight.Weight) + } + } } // AddEthHeader adds an Ethernet header to the flow. func (f *Flow) AddEthHeader() { eth := f.flow.Packet().Add().Ethernet() - eth.Src().SetValue(f.EthFlow.SrcMAC) - eth.Dst().SetValue(f.EthFlow.DstMAC) + if f.EthFlow.SrcMACCount != 0 { + eth.Src().Increment().SetStart(f.EthFlow.SrcMAC).SetCount(f.EthFlow.SrcMACCount) + } else { + eth.Src().SetValue(f.EthFlow.SrcMAC) + } + if f.EthFlow.DstMAC != "" { + if f.EthFlow.DstMACCount != 0 { + eth.Dst().Increment().SetStart(f.EthFlow.DstMAC).SetCount(f.EthFlow.DstMACCount) + } else { + eth.Dst().SetValue(f.EthFlow.DstMAC) + } + } } // AddGREHeader adds a GRE header to the flow. @@ -136,16 +192,28 @@ func (f *Flow) AddGREHeader() { } } -// AddVlanHeader adds a VLAN header to the flow. -func (f *Flow) AddVlanHeader() { - f.flow.Packet().Add().Vlan().Id().SetValue(f.VLANFlow.VLANId) +// AddVLANHeader adds a VLAN header to the flow. +func (f *Flow) AddVLANHeader() { + if f.VLANFlow.VLANCount != 0 { + f.flow.Packet().Add().Vlan().Id().Increment().SetStart(f.VLANFlow.VLANId).SetCount(f.VLANFlow.VLANCount) + } else { + f.flow.Packet().Add().Vlan().Id().SetValue(f.VLANFlow.VLANId) + } } // AddMPLSHeader adds an MPLS header to the flow. func (f *Flow) AddMPLSHeader() { mplsHdr := f.flow.Packet().Add().Mpls() - mplsHdr.Label().SetValue(f.MPLSFlow.MPLSLabel) - mplsHdr.TrafficClass().SetValue(f.MPLSFlow.MPLSExp) + if f.MPLSFlow.MPLSLabelCount != 0 { + mplsHdr.Label().Increment().SetStart(f.MPLSFlow.MPLSLabel).SetCount(f.MPLSFlow.MPLSLabelCount) + } else { + mplsHdr.Label().SetValue(f.MPLSFlow.MPLSLabel) + } + if f.MPLSFlow.MPLSExpCount != 0 { + mplsHdr.TrafficClass().Increment().SetStart(f.MPLSFlow.MPLSExp).SetCount(f.MPLSFlow.MPLSExpCount) + } else { + mplsHdr.TrafficClass().SetValue(f.MPLSFlow.MPLSExp) + } } // AddIPv4Header adds an IPv4 header to the flow. @@ -164,6 +232,27 @@ func (f *Flow) AddIPv4Header() { if f.IPv4Flow.TTL != 0 { ipv4Hdr.TimeToLive().SetValue(f.IPv4Flow.TTL) } + if f.IPv4Flow.DSCP != 0 || f.IPv4Flow.DSCPCount != 0 { + if f.IPv4Flow.DSCPCount != 0 { + ipv4Hdr.Priority().Dscp().Phb().Increment().SetStart(f.IPv4Flow.DSCP).SetCount(f.IPv4Flow.DSCPCount) + } else { + ipv4Hdr.Priority().Dscp().Phb().SetValue(f.IPv4Flow.DSCP) + } + } + if f.IPv4Flow.TOS != 0 || f.IPv4Flow.TOSCount != 0 { + if f.IPv4Flow.TOSCount != 0 { + ipv4Hdr.Priority().Tos().Precedence().Increment().SetStart(f.IPv4Flow.TOS).SetCount(f.IPv4Flow.TOSCount) + } else { + ipv4Hdr.Priority().Tos().Precedence().SetValue(f.IPv4Flow.TOS) + } + } + if f.IPv4Flow.RawPriority != 0 || f.IPv4Flow.RawPriorityCount != 0 { + if f.IPv4Flow.RawPriorityCount != 0 { + ipv4Hdr.Priority().Raw().Increment().SetStart(f.IPv4Flow.RawPriority).SetCount(f.IPv4Flow.RawPriorityCount) + } else { + ipv4Hdr.Priority().Raw().SetValue(f.IPv4Flow.RawPriority) + } + } } // AddIPv6Header adds an IPv6 header to the flow. @@ -182,6 +271,13 @@ func (f *Flow) AddIPv6Header() { if f.IPv6Flow.HopLimit != 0 { ipv6Hdr.HopLimit().SetValue(f.IPv6Flow.HopLimit) } + if f.IPv6Flow.TrafficClass != 0 || f.IPv6Flow.TrafficClassCount != 0 { + if f.IPv6Flow.TrafficClassCount != 0 { + ipv6Hdr.TrafficClass().Increment().SetStart(f.IPv6Flow.TrafficClass).SetCount(f.IPv6Flow.TrafficClassCount) + } else { + ipv6Hdr.TrafficClass().SetValue(f.IPv6Flow.TrafficClass) + } + } } // AddTCPHeader adds a TCP header to the flow. diff --git a/internal/otg_helpers/otg_config_helpers/otgisisblockhelpers.go b/internal/otg_helpers/otg_config_helpers/otgisisblockhelpers.go new file mode 100644 index 00000000000..49999913fdd --- /dev/null +++ b/internal/otg_helpers/otg_config_helpers/otgisisblockhelpers.go @@ -0,0 +1,405 @@ +package otgconfighelpers + +import ( + "fmt" + "net" + "strings" + + "github.com/open-traffic-generator/snappi/gosnappi" +) + +// V4IsisStRouteInfo is a struct that contains the data needed to generate a V4 route in the ISIS topology. +type V4IsisStRouteInfo struct { + addressFirstOctet string + prefix int + count int +} + +// SetAddressFirstOctet sets the first octet of the address of the V4 route. +func (v *V4IsisStRouteInfo) SetAddressFirstOctet(addressFirstOctet string) *V4IsisStRouteInfo { + v.addressFirstOctet = addressFirstOctet + return v +} + +// SetPrefix sets the prefix of the V4 route. +func (v *V4IsisStRouteInfo) SetPrefix(prefix int) *V4IsisStRouteInfo { + v.prefix = prefix + return v +} + +// SetCount sets the count of the V4 route. +func (v *V4IsisStRouteInfo) SetCount(count int) *V4IsisStRouteInfo { + v.count = count + return v +} + +// V6IsisStRouteInfo is a struct that contains the data needed to generate a V6 route in the ISIS topology. +type V6IsisStRouteInfo struct { + addressFirstOctet string + prefix int + count int +} + +// SetAddressFirstOctet sets the first octet of the address of the V6 route. +func (v *V6IsisStRouteInfo) SetAddressFirstOctet(addressFirstOctet string) *V6IsisStRouteInfo { + v.addressFirstOctet = addressFirstOctet + return v +} + +// SetPrefix sets the prefix of the V6 route. +func (v *V6IsisStRouteInfo) SetPrefix(prefix int) *V6IsisStRouteInfo { + v.prefix = prefix + return v +} + +// SetCount sets the count of the V6 route. +func (v *V6IsisStRouteInfo) SetCount(count int) *V6IsisStRouteInfo { + v.count = count + return v +} + +// GridIsisData is a struct that contains the data needed to generate a grid of ISIS devices. +type GridIsisData struct { + blockName string + config gosnappi.Config + row int + col int + systemIDFirstOctet string + linkIP4FirstOctet int + nextLinkIP4ToUse net.IP + linkIP6FirstOctet string + v4StRoute *V4IsisStRouteInfo + v6StRoute *V6IsisStRouteInfo + linkMultiplier int +} + +// SetRow sets the row of the grid. +func (v *GridIsisData) SetRow(row int) *GridIsisData { + v.row = row + return v +} + +// SetCol sets the column of the grid. +func (v *GridIsisData) SetCol(col int) *GridIsisData { + v.col = col + return v +} + +// SetSystemIDFirstOctet sets the ISISsystem ID first octet of the grid. +// all the ISIS system IDs will be in the format of XX.XXXX.XXXX.XXXX.XX where XX is the first octet. +func (v *GridIsisData) SetSystemIDFirstOctet(firstOctSysID string) *GridIsisData { + v.systemIDFirstOctet = firstOctSysID + return v +} + +// SetLinkIP4FirstOctet sets the first octet of the link IP4 address. +// The link IP4 address will be in the format of XX.XX.XX.XX/31 where XX is the first octet. +func (v *GridIsisData) SetLinkIP4FirstOctet(oct int) *GridIsisData { + v.linkIP4FirstOctet = oct + v.nextLinkIP4ToUse = net.ParseIP(fmt.Sprintf("%d.0.0.0", oct)) + return v +} + +// NextLinkIP4ToUse returns a pointer to the next link IP4 address to use. +func (v *GridIsisData) NextLinkIP4ToUse() *net.IP { + return &v.nextLinkIP4ToUse +} + +// SetLinkIP6FirstOctet sets the first octet of the link IP6 address. +// The link IP6 address will be in the format of XX:XX:XX:XX:XX:XX:XX:XX/64 where XX is the first octet. +func (v *GridIsisData) SetLinkIP6FirstOctet(oct string) *GridIsisData { + v.linkIP6FirstOctet = oct + return v +} + +// SetLinkMultiplier sets the number of links between each pair of nodes in the grid. +func (v *GridIsisData) SetLinkMultiplier(multiplier int) *GridIsisData { + v.linkMultiplier = multiplier + return v +} + +// SetBlockName sets the name of the grid , this will be used to create the name of the ISIS devices +// in the grid. +func (v *GridIsisData) SetBlockName(blockName string) *GridIsisData { + v.blockName = blockName + return v +} + +// V4RouteInfo returns the default V4 route info for the grid. +func (v *GridIsisData) V4RouteInfo() *V4IsisStRouteInfo { + v.v4StRoute = &V4IsisStRouteInfo{ + addressFirstOctet: "10", + prefix: 32, + count: 1, + } + return v.v4StRoute +} + +// V6RouteInfo returns the default V6 route info for the grid. +func (v *GridIsisData) V6RouteInfo() *V6IsisStRouteInfo { + v.v6StRoute = &V6IsisStRouteInfo{ + addressFirstOctet: "10", + prefix: 64, + count: 1, + } + return v.v6StRoute +} + +// GridIsisTopo is a struct that contains the data needed to generate the topology of the grid. +type GridIsisTopo struct { + blockName string + gridNodes [][]int + devices []gosnappi.Device + linkIP4FirstOctet int + linkIP6FirstOctet string + linkMultiplier int +} + +// Connect connects one of the devices in the grid to the given emulated router. +func (v *GridIsisTopo) Connect(emuDev gosnappi.Device, rowIdx int, colIdx int, nextLinkIP4ToUse *net.IP) error { + devIdx := v.gridNodes[rowIdx][colIdx] + simDev := v.devices[devIdx] + emuIdx := len(v.devices) + if err := createLink(emuDev, simDev, emuIdx, devIdx, + v.linkIP4FirstOctet, v.linkIP6FirstOctet, 1, nextLinkIP4ToUse); err != nil { + return err + } + + return nil +} + +// Device returns the device at the given row and column index. +func (v *GridIsisTopo) Device(rowIdx int, colIdx int) gosnappi.Device { + devIdx := v.gridNodes[rowIdx][colIdx] + simDev := v.devices[devIdx] + return simDev +} + +// GenerateTopology generates the topology of the grid. +func (v *GridIsisData) GenerateTopology() (GridIsisTopo, error) { + gridTopo := GridIsisTopo{ + linkIP4FirstOctet: v.linkIP4FirstOctet, + linkIP6FirstOctet: v.linkIP6FirstOctet, + linkMultiplier: v.linkMultiplier, + blockName: v.blockName, + } + if v.row <= 1 || v.col <= 1 { + return gridTopo, fmt.Errorf("grid must have more than one row or col") + } + if v.row > 255 || v.col > 255 { + return gridTopo, fmt.Errorf("grid col and row must be less than or equal to 255") + } + + if len(v.systemIDFirstOctet) == 0 { + return gridTopo, fmt.Errorf("system ID first octet for ISIS must be configured") + } + + gridTopo.gridNodes = make([][]int, v.row) + for i := range gridTopo.gridNodes { + gridTopo.gridNodes[i] = make([]int, v.col) + } + + nodeIDx := 0 + for rowIdx := 0; rowIdx < v.row; rowIdx++ { + for colIdx := 0; colIdx < v.col; colIdx++ { + gridTopo.gridNodes[rowIdx][colIdx] = nodeIDx + if dev, err := createSimDev(v.config, nodeIDx, v.systemIDFirstOctet, rowIdx, colIdx, v.v4StRoute, v.v6StRoute, v.blockName); err != nil { + return gridTopo, err + } else { + gridTopo.devices = append(gridTopo.devices, dev) + nodeIDx++ + } + } + } + + for rowIdx, row1 := range gridTopo.gridNodes { + for colIdx, val1 := range row1 { + if colIdx+1 != v.col { + val2 := row1[colIdx+1] + if err := createLink(gridTopo.devices[val1], gridTopo.devices[val2], + val1, val2, v.linkIP4FirstOctet, v.linkIP6FirstOctet, v.linkMultiplier, &v.nextLinkIP4ToUse); err != nil { + return gridTopo, err + } + } + if rowIdx+1 != v.row { + row2 := gridTopo.gridNodes[rowIdx+1] + val2 := row2[colIdx] + if err := createLink(gridTopo.devices[val1], gridTopo.devices[val2], + val1, val2, v.linkIP4FirstOctet, v.linkIP6FirstOctet, v.linkMultiplier, &v.nextLinkIP4ToUse); err != nil { + return gridTopo, err + } + } + } + } + + return gridTopo, nil +} + +// NewGridIsisData creates a new GridIsisData struct. +func NewGridIsisData(c gosnappi.Config) GridIsisData { + return GridIsisData{ + config: c, + v4StRoute: nil, + v6StRoute: nil, + } +} + +// createSimDev creates a simulated device in the ISIS topology. +func createSimDev(config gosnappi.Config, nodeIDx int, systemIDFirstOctet string, srcIdx int, dstIDx int, v4RouteInfo *V4IsisStRouteInfo, v6RouteInfo *V6IsisStRouteInfo, blockName string) (gosnappi.Device, error) { + otgIdx := nodeIDx + 1 + var deviceName string + var teRtrID string + + if dstIDx == -1 { + deviceName = fmt.Sprintf("%s_%sd%d.sim.%d", blockName, systemIDFirstOctet, otgIdx, srcIdx) + teRtrID = fmt.Sprintf("%s.10.0.%d", systemIDFirstOctet, srcIdx) + + } else { + deviceName = fmt.Sprintf("%s_%sd%d.sim.%d.%d", blockName, systemIDFirstOctet, otgIdx, srcIdx, dstIDx) + teRtrID = fmt.Sprintf("%s.%d.%d.1", systemIDFirstOctet, srcIdx, dstIDx) + } + dev := config.Devices().Add().SetName(deviceName) + + // System ID is 6 octets long, the first octet is taken as input for the block , the rest 5 octets + // are used to identify the device in the grid , so if the node index in the HEX format is more + // than 10 octets then we this means we ran out of space. + systemIDLastOctets := fmt.Sprintf("%02x", otgIdx) + if len(systemIDLastOctets) > 10 { + return nil, fmt.Errorf("ran out of system ID space") + } + systemID := systemIDFirstOctet + strings.Repeat("0", 10-len(systemIDLastOctets)) + systemIDLastOctets + simRtrIsis := dev.Isis(). + SetName(deviceName + ".isis"). + SetSystemId(systemID) + + simRtrIsis.Basic(). + SetIpv4TeRouterId(teRtrID). + SetHostname(deviceName). + SetEnableWideMetric(false) + + if v4RouteInfo != nil { + v4Route := simRtrIsis.V4Routes().Add(). + SetName(simRtrIsis.Name() + ".isis.v4routes"). + SetLinkMetric(10). + SetOriginType(gosnappi.IsisV4RouteRangeOriginType.INTERNAL) + + ipv4Prefix := fmt.Sprintf("%s.0.%d.0", v4RouteInfo.addressFirstOctet, srcIdx+1) + if dstIDx != -1 { + ipv4Prefix = fmt.Sprintf("%s.%d.%d.0", v4RouteInfo.addressFirstOctet, srcIdx+1, dstIDx+1) + } + v4Route.Addresses().Add(). + SetAddress(ipv4Prefix). + SetPrefix(uint32(v4RouteInfo.prefix)). + SetCount(uint32(v4RouteInfo.count)) + } + + if v6RouteInfo != nil { + v6Route := simRtrIsis.V6Routes().Add(). + SetName(simRtrIsis.Name() + ".isis.v6routes"). + SetLinkMetric(10). + SetOriginType(gosnappi.IsisV6RouteRangeOriginType.INTERNAL) + + ipv6Prefix := fmt.Sprintf("%s:%d::0", v6RouteInfo.addressFirstOctet, srcIdx+1) + if dstIDx != -1 { + ipv6Prefix = fmt.Sprintf("%s:%d:%d::0", v6RouteInfo.addressFirstOctet, srcIdx+1, dstIDx+1) + } + + v6Route.Addresses().Add(). + SetAddress(ipv6Prefix). + SetPrefix(uint32(v6RouteInfo.prefix)). + SetCount(uint32(v6RouteInfo.count)) + } + + return dev, nil +} + +// createLink creates a simulated link between the two devices in the ISIS topology. +func createLink(d1 gosnappi.Device, d2 gosnappi.Device, IDx1 int, IDx2 int, linkIP4FirstOctet int, linkIP6FirstOctet string, linkMultiplier int, ipv4Add *net.IP) error { + d1name := d1.Name() + d2name := d2.Name() + + idx1 := IDx1 + 1 + idx2 := IDx2 + 1 + + for i := 0; i < linkMultiplier; i++ { + + eth1Name := fmt.Sprintf("%veth%d", d1name, len(d1.Ethernets().Items())+1) + eth2Name := fmt.Sprintf("%veth%d", d2name, len(d2.Ethernets().Items())+1) + isisInf1Name := fmt.Sprintf("%vIsisinf%d", d1name, len(d1.Isis().Interfaces().Items())+1) + isisInf2Name := fmt.Sprintf("%vIsisinf%d", d2name, len(d2.Isis().Interfaces().Items())+1) + + d1eth := d1.Ethernets().Add().SetName(eth1Name) + d1eth.Connection().SimulatedLink().SetRemoteSimulatedLink(eth2Name) + + d1.Isis().Interfaces().Add().SetName(isisInf1Name).SetEthName(eth1Name). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + d2eth := d2.Ethernets().Add().SetName(eth2Name) + d2eth.Connection().SimulatedLink().SetRemoteSimulatedLink(eth1Name) + + d2.Isis().Interfaces().Add().SetName(isisInf2Name).SetEthName(eth2Name). + SetNetworkType(gosnappi.IsisInterfaceNetworkType.POINT_TO_POINT) + + if linkIP4FirstOctet == 0 && len(linkIP6FirstOctet) == 0 { + return fmt.Errorf("first octet of IP4 or IP6 for link must be configured") + } + + if linkIP4FirstOctet != 0 { + ip1Name := fmt.Sprintf("%vip4", eth1Name) + ip2Name := fmt.Sprintf("%vip4", eth2Name) + ip1 := ipv4Add.String() + + *ipv4Add = nextIP(*ipv4Add) + if ip4 := (*ipv4Add).To4(); ip4 == nil || ip4[0] != byte(linkIP4FirstOctet) { + return fmt.Errorf("no free ipv4 address in the major subnet %d/8", linkIP4FirstOctet) + } + ip2 := ipv4Add.String() + + *ipv4Add = nextIP(*ipv4Add) + + d1eth.Ipv4Addresses().Add(). + SetName(ip1Name). + SetAddress(ip1). + SetGateway(ip2). + SetPrefix(31) + + d2eth.Ipv4Addresses().Add(). + SetName(ip2Name). + SetAddress(ip2). + SetGateway(ip1). + SetPrefix(31) + } + + if len(linkIP6FirstOctet) != 0 { + ip1Name := fmt.Sprintf("%vip6", eth1Name) + ip2Name := fmt.Sprintf("%vip6", eth2Name) + ip1 := fmt.Sprintf("%s::%d:%d:%d", linkIP6FirstOctet, idx1, idx2, i*2+1) + ip2 := fmt.Sprintf("%s::%d:%d:%d", linkIP6FirstOctet, idx1, idx2, i*2+2) + + d1eth.Ipv6Addresses().Add(). + SetName(ip1Name). + SetAddress(ip1). + SetGateway(ip2) + + d2eth.Ipv6Addresses().Add(). + SetName(ip2Name). + SetAddress(ip2). + SetGateway(ip1) + } + } + return nil +} + +// nextIP returns the next IPv4 or v6 address in the same subnet. +func nextIP(ip net.IP) net.IP { + next := make(net.IP, len(ip)) + copy(next, ip) + + for i := len(next) - 1; i >= 0; i-- { + next[i]++ + if next[i] > 0 { + break + } + } + return next +} diff --git a/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go b/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go index bbac0d33fe8..53a1001d7eb 100644 --- a/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go +++ b/internal/otg_helpers/otg_validation_helpers/otgvalidationhelpers.go @@ -45,6 +45,7 @@ type InterfaceParams struct { type FlowParams struct { Name string TolerancePct float32 + ExpectedLoss float32 } // IsIPv4Interfaceresolved validates that the IPv4 interface is resolved based on the interface configured using otgconfighelpers. @@ -54,9 +55,9 @@ func (v *OTGValidation) IsIPv4Interfaceresolved(t *testing.T, ate *ondatra.ATEDe return val.IsPresent() }).Await(t) if !ok { - return fmt.Errorf(`IPv4 %s gateway not resolved`, intf) + return fmt.Errorf("IPv4 %s gateway not resolved", intf) } - t.Logf(`IPv4 %s gateway resolved to: %s`, intf, val1) + t.Logf("IPv4 %s gateway resolved to: %s", intf, val1) } return nil } @@ -68,9 +69,9 @@ func (v *OTGValidation) IsIPv6Interfaceresolved(t *testing.T, ate *ondatra.ATEDe return val.IsPresent() }).Await(t) if !ok { - return fmt.Errorf(`IPv6 %s gateway not resolved`, intf) + return fmt.Errorf("IPv6 %s gateway not resolved", intf) } - t.Logf(`IPv6 %s gateway resolved to: %s`, intf, val1) + t.Logf("IPv6 %s gateway resolved to: %s", intf, val1) } return nil } @@ -93,10 +94,65 @@ func (v *OTGValidation) ValidateLossOnFlows(t *testing.T, ate *ondatra.ATEDevice // ValidatePortIsActive validates the OTG port status. func (v *OTGValidation) ValidatePortIsActive(t *testing.T, ate *ondatra.ATEDevice) error { for _, port := range v.Interface.Ports { - PortStatus := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(port).Link().State()) - if want := otg.Port_Link_UP; PortStatus != want { - return fmt.Errorf("Get(OTG port status): got %v, want %v", PortStatus, want) + portStatus := gnmi.Get(t, ate.OTG(), gnmi.OTG().Port(port).Link().State()) + if want := otg.Port_Link_UP; portStatus != want { + return fmt.Errorf("Get(OTG port status): got %v, want %v", portStatus, want) } } return nil } + +// ReturnLossPercentage validates the percentage of traffic loss on the flows. +func (v *OTGValidation) ReturnLossPercentage(t *testing.T, ate *ondatra.ATEDevice) float32 { + outPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v.Flow.Name).Counters().OutPkts().State()) + if outPkts == 0 { + t.Fatalf("Get(out packets for flow %q): got %v, want nonzero", v.Flow.Name, outPkts) + } + inPkts := gnmi.Get(t, ate.OTG(), gnmi.OTG().Flow(v.Flow.Name).Counters().InPkts().State()) + lossPct := 100 * float32(outPkts-inPkts) / float32(outPkts) + return lossPct +} + +// ValidateOTGISISTelemetry validates the isis adjancency states +func ValidateOTGISISTelemetry(t *testing.T, ate *ondatra.ATEDevice, expectedAdj map[string]interface{}) { + isisAdj := gnmi.GetAll(t, ate.OTG(), gnmi.OTG().IsisRouter(expectedAdj["IsisRouterName"].(string)).Adjacencies().AdjacencyAny().State()) + + for _, adj := range isisAdj { + if adj.LocalState.GetLevelType().String() != expectedAdj["LocalStateTypeExp"].(string) { + t.Errorf("didn't receive expected local state level. got: %v, expected: %v", adj.LocalState.GetLevelType().String(), expectedAdj["LocalStateTypeExp"]) + } + + if adj.LocalState.GetHoldTimer() != expectedAdj["LocalStateHoldTimeExp"] { + t.Errorf("didn't receive expected local state hold timer. got: %v, expected: %v", adj.LocalState.GetHoldTimer(), expectedAdj["LocalStateHoldTimeExp"]) + } + + localStateRestartingStatus := adj.LocalState.GetLocalRestartingStatus().GetCurrentState().String() + if localStateRestartingStatus != expectedAdj["LocalStateRestartStatusExp"].(string) { + t.Errorf("didn't receive expected local state restarting status. got: %v, expected: %v", localStateRestartingStatus, expectedAdj["LocalStateRestartStatusExp"]) + } + + localStateAttemptStatus := adj.LocalState.GetLocalRestartingStatus().GetLocalLastRestartingAttemptStatus().GetLocalLastRestartingAttemptStatusType().String() + if localStateAttemptStatus != expectedAdj["LocalStateLastAttemptExp"].(string) { + t.Errorf("didn't receive expected local restarting status. got: %v, expected: %v", localStateAttemptStatus, expectedAdj["LocalStateLastAttemptExp"]) + } + + if adj.NeighborState.GetLevelType().String() != expectedAdj["NeighborStateTypeExp"].(string) { + t.Errorf("didn't receive expected neighbor state level. got: %v, expected: %v", adj.NeighborState.GetLevelType().String(), expectedAdj["NeighborStateTypeExp"]) + } + + if adj.NeighborState.GetHoldTimer() != expectedAdj["NeighborStateHoldTimeExp"] { + t.Errorf("didn't receive expected neighbor state hold timer. got: %v, expected: %v", adj.NeighborState.GetHoldTimer(), expectedAdj["NeighborStateHoldTimeExp"]) + } + + neighRestartingState := adj.NeighborState.GetNeighRestartingStatus().GetCurrentState().String() + if neighRestartingState != expectedAdj["NeighborStateRestartStatusExp"].(string) { + t.Errorf("didn't receive expected neighbor state restarting status. got: %v, expected: %v", neighRestartingState, expectedAdj["NeighborStateRestartStatusExp"]) + } + + neighLastAttemptStatus := adj.NeighborState.GetNeighRestartingStatus().GetNeighLastRestartingAttemptStatus().GetNeighLastRestartingAttemptStatusType().String() + if neighLastAttemptStatus != expectedAdj["NeighborStateLastAttemptExp"].(string) { + t.Errorf("didn't receive expected neighbor state last restart attempt status. got: %v, expected: %v", neighLastAttemptStatus, expectedAdj["NeighborStateLastAttemptExp"]) + } + } + +} diff --git a/internal/otg_helpers/packetvalidationhelpers/packetvalidationhelpers.go b/internal/otg_helpers/packetvalidationhelpers/packetvalidationhelpers.go new file mode 100644 index 00000000000..e1b0d116064 --- /dev/null +++ b/internal/otg_helpers/packetvalidationhelpers/packetvalidationhelpers.go @@ -0,0 +1,429 @@ +// Package packetvalidationhelpers provides helper functions to setup Protocol configurations on traffic generators. +package packetvalidationhelpers + +import ( + "fmt" + "os" + "testing" + + "github.com/google/gopacket" + "github.com/google/gopacket/layers" + "github.com/google/gopacket/pcap" + "github.com/open-traffic-generator/snappi/gosnappi" + "github.com/openconfig/ondatra" +) + +/* + +For Encap traffic validation, the following validations are performed: +1. Validate the outer IPv4 header (ValidateIPv4Header). +2. Validate the MPLS layer (ValidateMPLSLayer). +3. Validate the inner IPv4 header (ValidateInnerIPv4Header). + +Validations = []packetvalidationhelpers.ValidationType{ + packetvalidationhelpers.ValidateIPv4Header, + packetvalidationhelpers.ValidateMPLSLayer, + packetvalidationhelpers.ValidateInnerIPv4Header, + } + + EncapPacketValidation = &packetvalidationhelpers.PacketValidation{ + PortName: "port3", + Ipv4layer: IPlayerIPv4, + MplsLayer: MplsLayer, + Validations: Validations, + InnerIplayerIPv4: InnerIPlayerIPv4, + InnerIplayerIPv6: InnerIPlayerIPv6, + TCPLayer: &packetvalidationhelpers.TCPLayer{SrcPort: 49152, DstPort: 179}, + UDPLayer: &packetvalidationhelpers.UDPLayer{SrcPort: 49152, DstPort: 3784}, + } + + createflow(t, top, FlowIPv4, true) + packetvalidationhelpers.ConfigurePacketCapture(t, top, []string{"port3"}, EncapPacketValidation) + sendTrafficCapture(t, ate) + if err := FlowIPv4Validation.ValidateLossOnFlows(t, ate); err != nil { + t.Errorf("ValidateLossOnFlows(): got err: %q", err) + } + + if err := packetvalidationhelpers.CaptureAndValidatePackets(t, ate, EncapPacketValidation); err != nil { + t.Errorf("CaptureAndValidatePackets(): got err: %q", err) + } + + For Decap traffic validation, the following validation is performed: +1. Validate the IPv4 header : actual customer traffic (ValidateIPv4Header). +2. Validate the IPV6 header (ValidateIPv6Header). +*/ + +// IPv4 and IPv6 are the IP protocol types. +const ( + IPv4 = "IPv4" + IPv6 = "IPv6" + TCP = 6 // TCP protocol number as seen on the wire. + UDP = 17 // UDP protocol number as seen on the wire. +) + +// ValidationType defines the type of validation to perform. +type ValidationType string + +const ( + // ValidateIPv4Header validates the IPv4 header. + ValidateIPv4Header ValidationType = "ValidateIPv4Header" + // ValidateIPv6Header validates the IPv6 header. + ValidateIPv6Header ValidationType = "ValidateIPv6Header" + // ValidateInnerIPv4Header validates the inner IPv4 header. + ValidateInnerIPv4Header ValidationType = "ValidateInnerIPv4Header" + // ValidateInnerIPv6Header validates the inner IPv6 header. + ValidateInnerIPv6Header ValidationType = "ValidateInnerIPv6Header" + // ValidateMPLSLayer validates the MPLS layer. + ValidateMPLSLayer ValidationType = "ValidateMPLSLayer" + // ValidateTCPHeader validates the TCP header. + ValidateTCPHeader ValidationType = "ValidateTCPHeader" + // ValidateUDPHeader validates the UDP header. + ValidateUDPHeader ValidationType = "ValidateUDPHeader" +) + +// PacketValidation is a struct to hold the packet validation parameters. +type PacketValidation struct { + PortName string + CaptureName string + CaptureCount int + IPv4Layer *IPv4Layer + IPv6Layer *IPv6Layer + GreLayer *GreLayer + MPLSLayer *MPLSLayer + TCPLayer *TCPLayer + UDPLayer *UDPLayer + InnerIPLayerIPv4 *IPv4Layer + InnerIPLayerIPv6 *IPv6Layer + // Validations is a list of validations to perform on the captured packets. + Validations []ValidationType +} + +// IPv4Layer is a struct to hold the IP layer parameters. +type IPv4Layer struct { + Protocol uint32 + DstIP string + Tos uint8 + TTL uint8 +} + +// IPv6Layer is a struct to hold the IP layer parameters. +type IPv6Layer struct { + DstIP string + TrafficClass uint8 + HopLimit uint8 + NextHeader uint32 +} + +// GreLayer is a struct to hold the GRE layer parameters. +type GreLayer struct { + Protocol uint32 +} + +// MPLSLayer holds MPLS layer properties +type MPLSLayer struct { + Label uint32 + Tc uint8 +} + +// TCPLayer holds the TCP layer parameters. +type TCPLayer struct { + SrcPort uint32 + DstPort uint32 +} + +// UDPLayer holds the UDP layer parameters. +type UDPLayer struct { + SrcPort uint32 + DstPort uint32 +} + +// StartCapture starts the capture on the port. +func StartCapture(t *testing.T, ate *ondatra.ATEDevice) gosnappi.ControlState { + t.Helper() + + cs := gosnappi.NewControlState() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.START) + ate.OTG().SetControlState(t, cs) + + return cs +} + +// StopCapture stops the capture on the port. +func StopCapture(t *testing.T, ate *ondatra.ATEDevice, cs gosnappi.ControlState) { + t.Helper() + cs.Port().Capture().SetState(gosnappi.StatePortCaptureState.STOP) + ate.OTG().SetControlState(t, cs) +} + +// CaptureAndValidatePackets captures the packets and validates the traffic. +func CaptureAndValidatePackets(t *testing.T, ate *ondatra.ATEDevice, packetVal *PacketValidation) error { + t.Helper() + bytes := ate.OTG().GetCapture(t, gosnappi.NewCaptureRequest().SetPortName(packetVal.PortName)) + f, err := os.CreateTemp("", "pcap") + if err != nil { + return fmt.Errorf("could not create temporary pcap file: %v", err) + } + if _, err := f.Write(bytes); err != nil { + return fmt.Errorf("could not write bytes to pcap file: %v", err) + } + defer os.Remove(f.Name()) // Clean up the temporary file + f.Close() + + handle, err := pcap.OpenOffline(f.Name()) + if err != nil { + t.Fatalf("could not open pcap file") + return err + } + defer handle.Close() + + packetSource := gopacket.NewPacketSource(handle, handle.LinkType()) + + // Iterate over the validations specified in packetVal.Validations. + for _, validation := range packetVal.Validations { + switch validation { + case ValidateIPv4Header: + if err := validateIPv4Header(t, packetSource, packetVal); err != nil { + return err + } + case ValidateInnerIPv4Header: + if err := validateInnerIPv4Header(t, packetSource, packetVal); err != nil { + return err + } + case ValidateIPv6Header: + if err := validateIPv6Header(t, packetSource, packetVal); err != nil { + return err + } + case ValidateInnerIPv6Header: + if err := validateInnerIPv6Header(t, packetSource, packetVal); err != nil { + return err + } + case ValidateMPLSLayer: + if err := validateMPLSLayer(t, packetSource, packetVal); err != nil { + return err + } + case ValidateTCPHeader: + if err := validateTCPHeader(t, packetSource, packetVal); err != nil { + return err + } + case ValidateUDPHeader: + if err := validateUDPHeader(t, packetSource, packetVal); err != nil { + return err + } + default: + return fmt.Errorf("unknown validation type: %s", validation) + } + } + return nil +} + +// ClearCapture clears the capture on the port. +func ClearCapture(t *testing.T, top gosnappi.Config, ate *ondatra.ATEDevice) { + t.Helper() + top.Captures().Clear() + ate.OTG().PushConfig(t, top) +} + +// validateIPv4Header validates the outer IPv4 header. +func validateIPv4Header(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating IPv4 header") + + for packet := range packetSource.Packets() { + t.Logf("packet: %v", packet) + if ipLayer := packet.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv4) + + if uint32(ip.Protocol) != packetVal.IPv4Layer.Protocol { + return fmt.Errorf("packet is not encapsulated properly. Encapsulated protocol is: %d, expected: %d", ip.Protocol, packetVal.IPv4Layer.Protocol) + } + if ip.DstIP.String() != packetVal.IPv4Layer.DstIP { + return fmt.Errorf("IP Dst IP is not set properly. Expected: %s, Actual: %s", packetVal.IPv4Layer.DstIP, ip.DstIP) + } + if ip.TTL != packetVal.IPv4Layer.TTL { + return fmt.Errorf("IP TTL value is altered to: %d, expected: %d", ip.TTL, packetVal.IPv4Layer.TTL) + } + if ip.TOS != packetVal.IPv4Layer.Tos { + return fmt.Errorf("DSCP(TOS) value is altered to: %d, expected: %d", ip.TOS, packetVal.IPv4Layer.Tos) + } + // If validation is successful for one packet, we can return. + return nil + } + } + return fmt.Errorf("no IPv4 packets found") +} + +// validateIPv6Header validates the outer IPv4 header. +func validateIPv6Header(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating IPv6 header") + + for packet := range packetSource.Packets() { + t.Logf("packet: %v", packet) + if ipLayer := packet.Layer(layers.LayerTypeIPv6); ipLayer != nil { + ipv6, _ := ipLayer.(*layers.IPv6) + + if ipv6.DstIP.String() != packetVal.IPv6Layer.DstIP { + return fmt.Errorf("IPv6 Dst IP is not set properly. Expected: %s, Actual: %s", packetVal.IPv6Layer.DstIP, ipv6.DstIP) + } + if ipv6.HopLimit != packetVal.IPv6Layer.HopLimit { + return fmt.Errorf("IPv6 HopLimit value is altered to: %d. Expected: %d", ipv6.HopLimit, packetVal.IPv6Layer.HopLimit) + } + if ipv6.TrafficClass != packetVal.IPv6Layer.TrafficClass { + return fmt.Errorf("traffic class value is altered to: %d. expected: %d", ipv6.TrafficClass, packetVal.IPv6Layer.TrafficClass) + } + if packetVal.IPv6Layer.NextHeader != 0 { + if uint32(ipv6.NextHeader) != packetVal.IPv6Layer.NextHeader { + return fmt.Errorf("next header value is altered to: %d. expected: %d", ipv6.NextHeader, packetVal.IPv6Layer.NextHeader) + } + } + // If validation is successful for one packet, we can return. + return nil + } + } + return fmt.Errorf("no IPv6 packets found") +} + +// validateInnerIPv4Header validates the inner IPv4 header. +func validateInnerIPv4Header(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating inner IPv4 header") + + for packet := range packetSource.Packets() { + if greLayer := packet.Layer(layers.LayerTypeGRE); greLayer != nil { + gre := greLayer.(*layers.GRE) + encapPacket := gopacket.NewPacket(gre.Payload, gre.NextLayerType(), gopacket.Default) + + if ipLayer := encapPacket.Layer(layers.LayerTypeIPv4); ipLayer != nil { + ip, _ := ipLayer.(*layers.IPv4) + + if ip.DstIP.String() != packetVal.InnerIPLayerIPv4.DstIP { + return fmt.Errorf("IP Dst IP is not set properly. Expected: %s, Actual: %s", packetVal.InnerIPLayerIPv4.DstIP, ip.DstIP) + } + if ip.TTL != packetVal.InnerIPLayerIPv4.TTL { + return fmt.Errorf("IP TTL value is altered to: %d. Expected: %d", ip.TTL, packetVal.InnerIPLayerIPv4.TTL) + } + if ip.TOS != packetVal.InnerIPLayerIPv4.Tos { + return fmt.Errorf("DSCP(TOS) value is altered to: %d .Expected: %d", ip.TOS, packetVal.InnerIPLayerIPv4.Tos) + } + if packetVal.InnerIPLayerIPv4.Protocol != 0 { + if uint32(ip.Protocol) != packetVal.InnerIPLayerIPv4.Protocol { + return fmt.Errorf("protocol value is altered to: %d. expected: %d", ip.Protocol, packetVal.InnerIPLayerIPv4.Protocol) + } + } + // If validation is successful for one packet, we can return. + return nil + } + } + } + return fmt.Errorf("no inner IPv4 packets found") +} + +// validateInnerIPv6Header validates the inner IPv6 header. +func validateInnerIPv6Header(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating inner IPv6 header") + + for packet := range packetSource.Packets() { + if greLayer := packet.Layer(layers.LayerTypeGRE); greLayer != nil { + gre := greLayer.(*layers.GRE) + encapPacket := gopacket.NewPacket(gre.Payload, gre.NextLayerType(), gopacket.Default) + + if ipv6Layer := encapPacket.Layer(layers.LayerTypeIPv6); ipv6Layer != nil { + ipv6, _ := ipv6Layer.(*layers.IPv6) + if ipv6.DstIP.String() != packetVal.InnerIPLayerIPv6.DstIP { + return fmt.Errorf("IPv6 Dst IP is not set properly. Expected: %s, Actual: %s", packetVal.InnerIPLayerIPv6.DstIP, ipv6.DstIP) + } + if ipv6.HopLimit != packetVal.InnerIPLayerIPv6.HopLimit { + return fmt.Errorf("IPv6 HopLimit value is altered to: %d. Expected: %d", ipv6.HopLimit, packetVal.InnerIPLayerIPv6.HopLimit) + } + if ipv6.TrafficClass != packetVal.InnerIPLayerIPv6.TrafficClass { + return fmt.Errorf("traffic class value is altered to: %d. expected: %d", ipv6.TrafficClass, packetVal.InnerIPLayerIPv6.TrafficClass) + } + if packetVal.InnerIPLayerIPv6.NextHeader != 0 { + if uint32(ipv6.NextHeader) != packetVal.InnerIPLayerIPv6.NextHeader { + return fmt.Errorf("next header value is altered to: %d. expected: %d", ipv6.NextHeader, packetVal.InnerIPLayerIPv6.NextHeader) + } + } + // If validation is successful for one packet, we can return. + return nil + } + } + } + return fmt.Errorf("no inner IPv6 packets found") +} + +// validateMPLSLayer validates the MPLS layer. +func validateMPLSLayer(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating MPLS layer") + + for packet := range packetSource.Packets() { + if mplsLayer := packet.Layer(layers.LayerTypeMPLS); mplsLayer != nil { + mpls, _ := mplsLayer.(*layers.MPLS) + + if mpls.Label != packetVal.MPLSLayer.Label { + return fmt.Errorf("mpls label is not set properly. expected: %d, Actual: %d", packetVal.MPLSLayer.Label, mpls.Label) + } + if mpls.TrafficClass != packetVal.MPLSLayer.Tc { + return fmt.Errorf("mpls traffic class is not set properly. expected: %d, actual: %d", packetVal.MPLSLayer.Tc, mpls.TrafficClass) + } + // If validation is successful for one packet, we can return. + return nil + } + } + return fmt.Errorf("no MPLS packets found") +} + +// validateTCPHeader validates the TCP header. +func validateTCPHeader(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating TCP header") + + for packet := range packetSource.Packets() { + if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil { + tcp, _ := tcpLayer.(*layers.TCP) + + if uint32(tcp.DstPort) != packetVal.TCPLayer.DstPort { + return fmt.Errorf("TCP Dst Port is not set properly. Expected: %d, Actual: %d", packetVal.TCPLayer.DstPort, tcp.DstPort) + } + if uint32(tcp.SrcPort) != packetVal.TCPLayer.SrcPort { + return fmt.Errorf("TCP Src Port is not set properly. Expected: %d, Actual: %d", packetVal.TCPLayer.SrcPort, tcp.SrcPort) + } + // If validation is successful for one packet, we can return. + return nil + } + } + return fmt.Errorf("no TCP packets found") +} + +// validateUDPHeader validates the UDP header. +func validateUDPHeader(t *testing.T, packetSource *gopacket.PacketSource, packetVal *PacketValidation) error { + t.Helper() + t.Log("Validating UDP header") + + for packet := range packetSource.Packets() { + if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil { + udp, _ := udpLayer.(*layers.UDP) + + if uint32(udp.DstPort) != packetVal.UDPLayer.DstPort { + return fmt.Errorf("UDP Dst Port is not set properly. Expected: %d, Actual: %d", packetVal.UDPLayer.DstPort, udp.DstPort) + } + if uint32(udp.SrcPort) != packetVal.UDPLayer.SrcPort { + return fmt.Errorf("UDP Src Port is not set properly. Expected: %d, Actual: %d", packetVal.UDPLayer.SrcPort, udp.SrcPort) + } + // If validation is successful for one packet, we can return. + return nil + } + } + return fmt.Errorf("no UDP packets found") +} + +// ConfigurePacketCapture configures the packet capture on the port. +func ConfigurePacketCapture(t *testing.T, top gosnappi.Config, packetVal *PacketValidation) { + t.Helper() + ports := []string{} + ports = append(ports, packetVal.PortName) + top.Captures().Add().SetName(packetVal.CaptureName). + SetPortNames(ports). + SetFormat(gosnappi.CaptureFormat.PCAP) +} diff --git a/internal/rundata/dut.go b/internal/rundata/dut.go index e1779ea9efd..26a8512f4f5 100644 --- a/internal/rundata/dut.go +++ b/internal/rundata/dut.go @@ -54,7 +54,7 @@ func (di *DUTInfo) setFromComponentChassis(ctx context.Context, y components.Y) chassisIDs, err := y.FindByType(ctx, chassisType) if err != nil { - glog.Errorf("Could not find chassis: %v", err) + glog.ErrorContextf(ctx, "Could not find chassis: %v", err) return } @@ -63,24 +63,24 @@ func (di *DUTInfo) setFromComponentChassis(ctx context.Context, y components.Y) componentPath := ocpath.Root().Component(chassisID) component, err := ygnmi.Get(ctx, y.Client, componentPath.State()) if err != nil { - glog.Errorf("Could not get chassis: %v", err) + glog.ErrorContextf(ctx, "Could not get chassis: %v", err) return } if glog.V(2) { if out, err := json.MarshalIndent(component, "", " "); err == nil { - glog.Infof("Chassis component: %s", string(out)) + glog.InfoContextf(ctx, "Chassis component: %s", string(out)) } } if di.Vendor == "" && component.MfgName != nil { di.Vendor = *component.MfgName - glog.V(2).Infof("Setting vendor from chassis mfg-name: %s", di.Vendor) + glog.V(2).InfoContextf(ctx, "Setting vendor from chassis mfg-name: %s", di.Vendor) } if di.Model == "" && component.PartNo != nil { di.Model = *component.PartNo - glog.V(2).Infof("Setting model from chassis part-no: %s", di.Model) + glog.V(2).InfoContextf(ctx, "Setting model from chassis part-no: %s", di.Model) } if desc := component.Description; desc != nil { @@ -91,13 +91,13 @@ func (di *DUTInfo) setFromComponentChassis(ctx context.Context, y components.Y) fallthrough case di.Model == "": di.Model = *desc - glog.V(2).Infof("Setting model from chassis description: %s", di.Model) + glog.V(2).InfoContextf(ctx, "Setting model from chassis description: %s", di.Model) } } if di.OSVer == "" && component.SoftwareVersion != nil { di.OSVer = *component.SoftwareVersion - glog.V(2).Infof("Setting osver from chassis software-version: %s", di.OSVer) + glog.V(2).InfoContextf(ctx, "Setting osver from chassis software-version: %s", di.OSVer) } } @@ -112,7 +112,7 @@ func (di *DUTInfo) setFromComponentOS(ctx context.Context, y components.Y) { osIDs, err := y.FindByType(ctx, osType) if err != nil { - glog.Errorf("Could not find operating-system: %v", err) + glog.ErrorContextf(ctx, "Could not find operating-system: %v", err) return } @@ -121,10 +121,10 @@ func (di *DUTInfo) setFromComponentOS(ctx context.Context, y components.Y) { softVerPath := ocpath.Root().Component(osID).SoftwareVersion() softVer, err := ygnmi.Get(ctx, y.Client, softVerPath.State()) if err != nil { - glog.Errorf("Missing component software-version: %v", err) + glog.ErrorContextf(ctx, "Missing component software-version: %v", err) } else { di.OSVer = softVer - glog.V(2).Infof("Setting osver from operating-system software-version: %s", di.OSVer) + glog.V(2).InfoContextf(ctx, "Setting osver from operating-system software-version: %s", di.OSVer) } } @@ -138,25 +138,25 @@ func (di *DUTInfo) setFromLLDP(ctx context.Context, y components.Y) { lldpPath := ocpath.Root().Lldp().SystemDescription() lldp, err := ygnmi.Get(ctx, y.Client, lldpPath.State()) if err != nil { - glog.Errorf("Could not get LLDP: %v", err) + glog.ErrorContextf(ctx, "Could not get LLDP: %v", err) } - glog.V(2).Infof("LLDP system-description: %s", lldp) + glog.V(2).InfoContextf(ctx, "LLDP system-description: %s", lldp) if juniper := "Juniper Networks, Inc."; strings.Contains(lldp, juniper) { di.Vendor = juniper - glog.Infof("Setting vendor from lldp system-description: %s", di.Vendor) + glog.InfoContextf(ctx, "Setting vendor from lldp system-description: %s", di.Vendor) } if srlinux := "SRLinux-v"; strings.HasPrefix(lldp, srlinux) { if di.Vendor == "" { di.Vendor = "Nokia" - glog.Infof("Setting vendor from lldp system-description: %s", di.Vendor) + glog.InfoContextf(ctx, "Setting vendor from lldp system-description: %s", di.Vendor) } if di.OSVer == "" { parts := strings.Split(lldp, " ") di.OSVer = parts[0][len(srlinux):] - glog.Infof("Setting osver from lldp system-description: %s", di.OSVer) + glog.InfoContextf(ctx, "Setting osver from lldp system-description: %s", di.OSVer) } } } @@ -172,10 +172,10 @@ func (di *DUTInfo) setFromSystem(ctx context.Context, y components.Y) { softVerPath := ocpath.Root().System().SoftwareVersion() softVer, err := ygnmi.Get(ctx, y.Client, softVerPath.State()) if err != nil { - glog.Errorf("Missing system software-version: %v", err) + glog.ErrorContextf(ctx, "Missing system software-version: %v", err) } else { di.OSVer = softVer - glog.V(2).Infof("Setting osver from system software-version: %s", di.OSVer) + glog.V(2).InfoContextf(ctx, "Setting osver from system software-version: %s", di.OSVer) } } @@ -268,12 +268,12 @@ func dutsInfo(ctx context.Context, m map[string]string, resv *binding.Reservatio for id, dut := range resv.DUTs { gnmic, err := dut.DialGNMI(ctx) if err != nil { - glog.Errorf("Could not dial GNMI to dut %s: %v", dut.Name(), err) + glog.ErrorContextf(ctx, "Could not dial GNMI to dut %s: %v", dut.Name(), err) continue } dInfo, err := NewDUTInfo(ctx, gnmic) if err != nil { - glog.Errorf("Could not get DUTInfo for dut %v: %v", dut.Name(), err) + glog.ErrorContextf(ctx, "Could not get DUTInfo for dut %v: %v", dut.Name(), err) continue } dInfo.put(m, id) diff --git a/internal/security/authz/authz.go b/internal/security/authz/authz.go index 75cd230f305..3aede3a0217 100644 --- a/internal/security/authz/authz.go +++ b/internal/security/authz/authz.go @@ -228,7 +228,7 @@ func Verify(t testing.TB, dut *ondatra.DUTDevice, spiffe *Spiffe, rpc *gnxi.RPC, expectedRes = authzpb.ProbeResponse_ACTION_DENY expectedExecErr = codes.PermissionDenied case *HardVerify: - hardVerify = true + hardVerify = false default: t.Errorf("Invalid option is passed to Verify function: %T", opt) } diff --git a/internal/security/credz/credz.go b/internal/security/credz/credz.go index 6a9213c8c39..1db6dc90e07 100644 --- a/internal/security/credz/credz.go +++ b/internal/security/credz/credz.go @@ -32,7 +32,7 @@ import ( "github.com/openconfig/ondatra/binding" "github.com/openconfig/ondatra/gnmi" "github.com/openconfig/ondatra/gnmi/oc" - "golang.org/x/crypto/ssh" + // "golang.org/x/crypto/ssh" ) const ( @@ -41,8 +41,8 @@ const ( digits = "0123456789" symbols = "!@#$%^&*(){}[]\\|:;\"'" space = " " - userKey = "testuser" dutKey = "dut" + userKey = "testuser" caKey = "ca" minPasswordLength = 24 maxPasswordLength = 32 @@ -279,7 +279,7 @@ func RotateAuthenticationArtifacts(t *testing.T, dut *ondatra.DUTDevice, keyDir, var artifactContents []*cpb.ServerKeysRequest_AuthenticationArtifacts if keyDir != "" { - data, err := os.ReadFile(fmt.Sprintf("%s/%s", keyDir, dutKey)) + data, err := os.ReadFile(fmt.Sprintf("%s/%s", keyDir, dut.ID())) if err != nil { t.Fatalf("Failed reading host private key, error: %s", err) } @@ -289,7 +289,7 @@ func RotateAuthenticationArtifacts(t *testing.T, dut *ondatra.DUTDevice, keyDir, } if certDir != "" { - data, err := os.ReadFile(fmt.Sprintf("%s/%s-cert.pub", certDir, dutKey)) + data, err := os.ReadFile(fmt.Sprintf("%s/%s-cert.pub", certDir, dut.ID())) if err != nil { t.Fatalf("Failed reading host signed certificate, error: %s", err) } @@ -402,19 +402,19 @@ func CreateUserCertificate(t *testing.T, dir, userPrincipal string) { } // CreateHostCertificate takes in dut key contents & creates ssh host certificate in the specified directory. -func CreateHostCertificate(t *testing.T, dir string, dutKeyContents []byte) { - err := os.WriteFile(fmt.Sprintf("%s/%s.pub", dir, dutKey), dutKeyContents, 0o777) +func CreateHostCertificate(t *testing.T, dut *ondatra.DUTDevice, dir string, dutKeyContents []byte) { + err := os.WriteFile(fmt.Sprintf("%s/%s.pub", dir, dut.ID()), dutKeyContents, 0o777) if err != nil { t.Fatalf("Failed writing dut public key to temp dir, error: %s", err) } cmd := exec.Command( "ssh-keygen", "-s", caKey, // sign using this ca key - "-I", dutKey, // key identity + "-I", dut.ID(), // key identity "-h", // create host (not user) certificate "-n", "dut.test.com", // principal(s) "-V", "+52w", // validity - fmt.Sprintf("%s.pub", dutKey), + fmt.Sprintf("%s.pub", dut.ID()), ) cmd.Dir = dir err = cmd.Run() @@ -423,7 +423,7 @@ func CreateHostCertificate(t *testing.T, dir string, dutKeyContents []byte) { } } -func createHibaKeysCopy(t *testing.T, dir string) { +func createHibaKeysCopy(t *testing.T, keysDir string) { keyFiles := []string{ "ca", "ca.pub", @@ -434,11 +434,11 @@ func createHibaKeysCopy(t *testing.T, dir string) { "users/testuser.pub", "users/testuser-cert.pub", } - err := os.Mkdir(fmt.Sprintf("%s/hosts", dir), 0o700) + err := os.Mkdir(fmt.Sprintf("%s/hosts", keysDir), 0o700) if err != nil { t.Fatalf("Failed ensuring hosts dir in temp dir, error: %s", err) } - err = os.Mkdir(fmt.Sprintf("%s/users", dir), 0o700) + err = os.Mkdir(fmt.Sprintf("%s/users", keysDir), 0o700) if err != nil { t.Fatalf("Failed ensuring users dir in temp dir, error: %s", err) } @@ -450,116 +450,108 @@ func createHibaKeysCopy(t *testing.T, dir string) { t.Errorf("Error reading file %v, error: %s", keyFile, err) return } - err = os.WriteFile(fmt.Sprintf("%s/%s", dir, keyFile), input, 0o600) + err = os.WriteFile(fmt.Sprintf("%s/%s", keysDir, keyFile), input, 0o600) if err != nil { t.Fatalf("Failed copying key file %s to temp test dir, error: %s", keyFile, err) } } } -func createHibaKeysGen(t *testing.T, dir string) { +func createHibaKeysGen(t *testing.T, hibaCa, hibaGen, keysDir string) { caCmd := exec.Command( - "hiba-ca.sh", + hibaCa, "-c", - "-d", dir, // output to the temp dir + "-d", keysDir, // output to the temp dir "--", // pass the rest to ssh-keygen "-q", "-N", "", // quiet, empty passphrase ) - caCmd.Dir = dir err := caCmd.Run() if err != nil { t.Fatalf("Failed generating ca key pair, error: %s", err) } userKeyCmd := exec.Command( - "hiba-ca.sh", + hibaCa, "-c", - "-d", dir, + "-d", keysDir, "-u", "-I", userKey, "--", "-q", "-N", "", ) - userKeyCmd.Dir = dir err = userKeyCmd.Run() if err != nil { t.Fatalf("Failed generating user key pair, error: %s", err) } dutKeyCmd := exec.Command( - "hiba-ca.sh", + hibaCa, "-c", - "-d", dir, + "-d", keysDir, "-h", "-I", dutKey, "--", "-q", "-N", "", ) - dutKeyCmd.Dir = dir err = dutKeyCmd.Run() if err != nil { t.Fatalf("Failed generating dut key pair, error: %s", err) } prodIdentityCmd := exec.Command( - "hiba-gen", + hibaGen, "-i", - "-f", fmt.Sprintf("%s/policy/identities/prod", dir), - "domain", "example.com", + "-f", fmt.Sprintf("%s/policy/identities/prod", keysDir), + "domain", "google.com", ) - prodIdentityCmd.Dir = dir err = prodIdentityCmd.Run() if err != nil { t.Fatalf("Failed creating prod identity, error: %s", err) } shellGrantCmd := exec.Command( - "hiba-gen", - "-f", fmt.Sprintf("%s/policy/grants/shell", dir), - "domain", "example.com", + hibaGen, + "-f", fmt.Sprintf("%s/policy/grants/shell", keysDir), + "domain", "google.com", ) - shellGrantCmd.Dir = dir err = shellGrantCmd.Run() if err != nil { t.Fatalf("Failed creating shell grant, error: %s", err) } grantShellToUserCmd := exec.Command( - "hiba-ca.sh", - "-d", dir, + hibaCa, + "-d", keysDir, "-p", "-I", userKey, "-H", "shell", ) - grantShellToUserCmd.Dir = dir err = grantShellToUserCmd.Run() if err != nil { t.Fatalf("Failed granting shell grant to testuser, error: %s", err) } createHostCertCmd := exec.Command( - "hiba-ca.sh", - "-d", dir, + hibaCa, + "-d", keysDir, "-s", "-h", "-I", dutKey, "-H", "prod", "-V", "+52w", ) - createHostCertCmd.Dir = dir err = createHostCertCmd.Run() if err != nil { t.Fatalf("Failed creating host certificate, error: %s", err) } createUserCertCmd := exec.Command( - "hiba-ca.sh", - "-d", dir, + hibaCa, + "-d", keysDir, "-s", "-u", "-I", userKey, "-H", "shell", ) - createUserCertCmd.Dir = dir err = createUserCertCmd.Run() if err != nil { t.Fatalf("Failed creating user certificate, error: %s", err) @@ -577,86 +569,53 @@ func createHibaKeysGen(t *testing.T, dir string) { // feature/security/gnsi/credentialz/tests/hiba_authentication/users/testuser, // feature/security/gnsi/credentialz/tests/hiba_authentication/users/testuser.pub, // feature/security/gnsi/credentialz/tests/hiba_authentication/users/testuser-cert.pub, -func CreateHibaKeys(t *testing.T, dir string) { +func CreateHibaKeys(t *testing.T, dut *ondatra.DUTDevice, keysDir string) { hibaCa, _ := exec.LookPath("hiba-ca.sh") hibaGen, _ := exec.LookPath("hiba-gen") if hibaCa == "" || hibaGen == "" { t.Log("hiba-ca and/or hiba-gen not found on path, will try to use certs in local test dir if present.") - createHibaKeysCopy(t, dir) + createHibaKeysCopy(t, keysDir) } else { - createHibaKeysGen(t, dir) + createHibaKeysGen(t, hibaCa, hibaGen, keysDir) } } // SSHWithPassword dials ssh with password based authentication to be used in credentialz tests. -func SSHWithPassword(target, username, password string) (*ssh.Client, error) { - return ssh.Dial( - "tcp", - target, - &ssh.ClientConfig{ - User: username, - Auth: []ssh.AuthMethod{ - ssh.Password(password), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), // lgtm[go/insecure-hostkeycallback] - }, - ) +func SSHWithPassword(ctx context.Context, dut *ondatra.DUTDevice, username, password string) (binding.SSHClient, error) { + return dut.RawAPIs().BindingDUT().DialSSH(ctx, binding.PasswordAuth{User: username, Password: password}) } // SSHWithCertificate dials ssh with user certificate to be used in credentialz tests. -func SSHWithCertificate(t *testing.T, target, username, dir string) (*ssh.Client, error) { +func SSHWithCertificate(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, username, dir string) (binding.SSHClient, error) { + privateKeyContents, err := os.ReadFile(fmt.Sprintf("%s/%s", dir, userKey)) if err != nil { t.Fatalf("Failed reading private key contents, error: %s", err) } - signer, err := ssh.ParsePrivateKey(privateKeyContents) - if err != nil { - t.Fatalf("Failed parsing private key, error: %s", err) - } + certificateContents, err := os.ReadFile(fmt.Sprintf("%s/%s-cert.pub", dir, userKey)) if err != nil { t.Fatalf("Failed reading certificate contents, error: %s", err) } - certificate, _, _, _, err := ssh.ParseAuthorizedKey(certificateContents) - if err != nil { - t.Fatalf("Failed parsing certificate contents, error: %s", err) - } - certificateSigner, err := ssh.NewCertSigner(certificate.(*ssh.Certificate), signer) - if err != nil { - t.Fatalf("Failed creating certificate signer, error: %s", err) - } - return ssh.Dial( - "tcp", - target, - sshClientConfigWithPublicKeys(username, certificateSigner), - ) + return dut.RawAPIs().BindingDUT().DialSSH(ctx, binding.CertificateAuth{User: username, PrivateKey: privateKeyContents, Certificate: certificateContents}) } // SSHWithKey dials ssh with key based authentication to be used in credentialz tests. -func SSHWithKey(t *testing.T, target, username, dir string) (*ssh.Client, error) { +func SSHWithKey(ctx context.Context, t *testing.T, dut *ondatra.DUTDevice, target, username, dir string) (binding.SSHClient, error) { privateKeyContents, err := os.ReadFile(fmt.Sprintf("%s/%s", dir, userKey)) if err != nil { t.Fatalf("Failed reading private key contents, error: %s", err) } - signer, err := ssh.ParsePrivateKey(privateKeyContents) - if err != nil { - t.Fatalf("Failed parsing private key, error: %s", err) - } - - return ssh.Dial( - "tcp", - target, - sshClientConfigWithPublicKeys(username, signer), - ) + return dut.RawAPIs().BindingDUT().DialSSH(ctx, binding.KeyAuth{User: username, Key: privateKeyContents}) } -func sshClientConfigWithPublicKeys(username string, signer ssh.Signer) *ssh.ClientConfig { - return &ssh.ClientConfig{ - User: username, - Auth: []ssh.AuthMethod{ - ssh.PublicKeys(signer), - }, - HostKeyCallback: ssh.InsecureIgnoreHostKey(), // lgtm[go/insecure-hostkeycallback] - } -} +// func sshClientConfigWithPublicKeys(username string, signer ssh.Signer) *ssh.ClientConfig { +// return &ssh.ClientConfig{ +// User: username, +// Auth: []ssh.AuthMethod{ +// ssh.PublicKeys(signer), +// }, +// HostKeyCallback: ssh.InsecureIgnoreHostKey(), // lgtm[go/insecure-hostkeycallback] +// } +// } diff --git a/internal/security/gen/generate.go b/internal/security/gen/generate.go index 51b68ce56cc..b4f9c19548b 100644 --- a/internal/security/gen/generate.go +++ b/internal/security/gen/generate.go @@ -68,18 +68,18 @@ var ( "gnsi.pathz": pathzpb.File_github_com_openconfig_gnsi_pathz_pathz_proto.Services(), "gnsi.cred": credpb.File_github_com_openconfig_gnsi_credentialz_credentialz_proto.Services(), "gnsi.acc": accpb.File_github_com_openconfig_gnsi_acctz_acctz_proto.Services(), - "gnoi.bgp": bpb.File_bgp_bgp_proto.Services(), - "gnoi.diag": dpb.File_diag_diag_proto.Services(), - "gnoi.factory_reset": frpb.File_factory_reset_factory_reset_proto.Services(), - "gnoi.file": fpb.File_file_file_proto.Services(), - "gnoi.healthz": hpb.File_healthz_healthz_proto.Services(), - "gnoi.layer2": lpb.File_layer2_layer2_proto.Services(), - "gnoi.mppls": mpb.File_mpls_mpls_proto.Services(), + "gnoi.bgp": bpb.File_github_com_openconfig_gnoi_bgp_bgp_proto.Services(), + "gnoi.diag": dpb.File_github_com_openconfig_gnoi_diag_diag_proto.Services(), + "gnoi.factory_reset": frpb.File_github_com_openconfig_gnoi_factory_reset_factory_reset_proto.Services(), + "gnoi.file": fpb.File_github_com_openconfig_gnoi_file_file_proto.Services(), + "gnoi.healthz": hpb.File_github_com_openconfig_gnoi_healthz_healthz_proto.Services(), + "gnoi.layer2": lpb.File_github_com_openconfig_gnoi_layer2_layer2_proto.Services(), + "gnoi.mppls": mpb.File_github_com_openconfig_gnoi_mpls_mpls_proto.Services(), "gnoi.os": ospb.File_github_com_openconfig_gnoi_os_os_proto.Services(), - "gnoi.otdr": otpb.File_otdr_otdr_proto.Services(), - "gnoi.link_qualification": plqpb.File_packet_link_qualification_packet_link_qualification_proto.Services(), - "gnoi.system": spb.File_system_system_proto.Services(), - "gnoi.wpb": wpb.File_wavelength_router_wavelength_router_proto.Services(), + "gnoi.otdr": otpb.File_github_com_openconfig_gnoi_otdr_otdr_proto.Services(), + "gnoi.link_qualification": plqpb.File_github_com_openconfig_gnoi_packet_link_qualification_packet_link_qualification_proto.Services(), + "gnoi.system": spb.File_github_com_openconfig_gnoi_system_system_proto.Services(), + "gnoi.wpb": wpb.File_github_com_openconfig_gnoi_wavelength_router_wavelength_router_proto.Services(), "p4rt": p4rtpb.File_p4_v1_p4runtime_proto.Services(), } ) diff --git a/internal/telemetry/aftcache/aft_cache.go b/internal/telemetry/aftcache/aft_cache.go new file mode 100644 index 00000000000..9c2e0184056 --- /dev/null +++ b/internal/telemetry/aftcache/aft_cache.go @@ -0,0 +1,880 @@ +// Copyright 2025 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 aftcache is a test library for storing a stream of AFT telemetry at full RIB scale +// in a local cache so we can periodically check if required test conditions are met, +// such as verifying that all expected prefixes are present. +package aftcache + +import ( + "context" + "errors" + "fmt" + "os" + "path/filepath" + "slices" + "strconv" + "strings" + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/openconfig/featureprofiles/internal/deviations" + "github.com/openconfig/featureprofiles/internal/telemetry/schema" + "github.com/openconfig/gnmi/cache" + "github.com/openconfig/gnmi/ctree" + "github.com/openconfig/gnmi/metadata" + "github.com/openconfig/ondatra" + "github.com/openconfig/ygot/ygot" + + log "github.com/golang/glog" + gnmipb "github.com/openconfig/gnmi/proto/gnmi" +) + +const ( + prefixPathV4 = "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/prefix" + prefixNHGPathV4 = "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group" + prefixPathV6 = "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/prefix" + prefixNHGPathV6 = "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group" + nextHopWeightPath = "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/state/weight" + nextHopGroupConditionPath = "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/condition" + // periodicInterval is the time between execution of periodic hooks. + periodicInterval = 2 * time.Minute + // periodicDeadline is the deadline for all periodic hooks in a run. Should be < periodicInterval. + periodicDeadline = 1*time.Minute + 45*time.Second + // aftBufferSize is the capacity of the internal channel queueing notifications from DUT + // before applying them to our internal cache. It should be large enough to prevent DUT from + // timing out from a pending send longer than the timeout while our internal cache is + // locked because a periodic hook is running, which could take up to periodicDeadline. The channel + // is preallocated, using a 64bit pointer per slot. We expect somewhat increased memory usage on + // heap from a higher buffer value just because the buffer may contain multiple updates for the + // same leaf, while our internal cache would not. + // We expect the buffer to be large enough to hold 2M IPv4 prefixes and 1M IPv6 prefixes. + aftBufferSize = 4000000 + // missingPrefixesFile is the name of the file where missing prefixes are written. + missingPrefixesFile = "missing_prefixes.txt" +) + +var ( + // ErrNotExist is an error returned when expected AFT elements are not found and so AFT is inconsistent. + ErrNotExist = errors.New("does not exist") + // ErrUnsupported is an error returned when AFT elements are not supported. + ErrUnsupported = errors.New("unsupported") + missingPrefixes = make(map[string]bool) +) + +var unusedPaths = []string{ + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/prefix", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/octets-forwarded", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/counters/packets-forwarded", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/decapsulate-header", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/entry-metadata", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/next-hop-group-network-instance", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-network-instance", + "/network-instances/network-instance/afts/ipv4-unicast/ipv4-entry/state/origin-protocol", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/prefix", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/octets-forwarded", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/counters/packets-forwarded", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/decapsulate-header", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/entry-metadata", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/next-hop-group-network-instance", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-network-instance", + "/network-instances/network-instance/afts/ipv6-unicast/ipv6-entry/state/origin-protocol", + "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/id", + "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/next-hops/next-hop/index", + "/network-instances/network-instance/afts/next-hop-groups/next-hop-group/state/backup-next-hop-group", + "/network-instances/network-instance/afts/next-hops/next-hop/index", + "/network-instances/network-instance/afts/next-hops/next-hop/interface-ref/state/subinterface", + "/network-instances/network-instance/afts/next-hops/next-hop/state/counters/octets-forwarded", + "/network-instances/network-instance/afts/next-hops/next-hop/state/counters/packets-forwarded", + "/network-instances/network-instance/afts/next-hops/next-hop/state/encapsulate-header", + "/network-instances/network-instance/afts/next-hops/next-hop/state/mac-address", + "/network-instances/network-instance/afts/next-hops/next-hop/state/origin-protocol", +} + +func subscriptionPaths(dut *ondatra.DUTDevice) map[string][]string { + defaultNetworkInstance := deviations.DefaultNetworkInstance(dut) + return map[string][]string{ + "prefix": { + fmt.Sprintf("network-instances/network-instance[name=%s]/afts/ipv4-unicast/ipv4-entry", defaultNetworkInstance), + fmt.Sprintf("network-instances/network-instance[name=%s]/afts/ipv6-unicast/ipv6-entry", defaultNetworkInstance), + }, + "nhg": { + fmt.Sprintf("network-instances/network-instance[name=%s]/afts/next-hop-groups/next-hop-group", defaultNetworkInstance), + }, + "nh": { + fmt.Sprintf("network-instances/network-instance[name=%s]/afts/next-hops/next-hop", defaultNetworkInstance), + }, + } +} + +// AFTData represents an AFT and provides methods for resolving routes. +type AFTData struct { + // Prefixes contains a map of prefixes to their corresponding next hop group IDs. + Prefixes map[string]uint64 + // NextHopGroups contains a map of next hop group IDs to their corresponding next hop group data. + NextHopGroups map[uint64]*aftNextHopGroup + // NextHops contains a map of next hop IDs to their corresponding next hop data. + NextHops map[uint64]*aftNextHop +} + +// aftCache is the AFT streaming cache. +type aftCache struct { + cache *cache.Cache // Cache used to store AFT notifications during streaming. + target string +} + +// aftNextHopGroup represents an AFT next hop group. +type aftNextHopGroup struct { + // NHIDs contains the next hop IDs that are part of this next hop group. + NHIDs []uint64 + // NHWeights contains the weights of the next hops in this next hop group. + NHWeights map[uint64]uint64 + // Conditionals contains the conditionals that are part of this next hop group. + Conditionals []*aftNextHopGroupConditional +} + +// aftNextHopGroupConditional represents a condition for an AFT next hop group. +type aftNextHopGroupConditional struct { + // DSCP contains the DSCP bits that are part of this conditional. + DSCP []uint8 + // NHGID contains the next hop group ID that is part of this conditional. + NHGID uint64 +} + +// aftNextHop represents an AFT next hop. +type aftNextHop struct { + // IntfName contains the interface name of the next hop. + IntfName string + // IP contains the IP address of the next hop. + IP string + // LSPName contains the LSP name of the next hop. + LSPName string +} + +// generateCacheTraversalPaths converts a map of subscription paths to a map of cache traversal paths. +// For example: +// input: +// +// map[string][]string{ +// "prefix": []string{ +// "network-instances/network-instance[name=DEFAULT]/afts/ipv4-unicast/ipv4-entry", +// }, +// } +// +// output: +// +// map[string][]string{ +// "prefix": []string{ +// "openconfig/network-instances/network-instance/name/DEFAULT/afts/ipv4-unicast/ipv4-entry", +// }, +// } +func generateCacheTraversalPaths(subscriptionPaths map[string][]string) (map[string][]string, error) { + cachePaths := make(map[string][]string) + for key, paths := range subscriptionPaths { + var currentPaths []string + for _, p := range paths { + sp, err := ygot.StringToStructuredPath(p) + if err != nil { + return nil, fmt.Errorf("error parsing path %s: %w", p, err) + } + outParts := []string{"openconfig"} + for _, elem := range sp.Elem { + outParts = append(outParts, elem.Name) + for _, keyVal := range elem.Key { + outParts = append(outParts, keyVal) + } + } + currentPaths = append(currentPaths, strings.Join(outParts, "/")) + } + cachePaths[key] = currentPaths + } + return cachePaths, nil +} + +// ToAFT Creates AFT maps with cache information. +func (c *aftCache) ToAFT(dut *ondatra.DUTDevice) (*AFTData, error) { + a := newAFT() + prefixFunc := func(n *gnmipb.Notification) error { + p, nhg, err := parsePrefix(n) + if err != nil { + return err + } + a.Prefixes[p] = nhg + return nil + } + nhgFunc := func(n *gnmipb.Notification) error { + nhg, data, err := parseNHG(n) + switch { + case errors.Is(err, ErrNotExist) || errors.Is(err, ErrUnsupported): + log.Warningf("error parsing NHG: %v", err) + case err != nil: + return err + default: + a.NextHopGroups[nhg] = data + } + return nil + } + nhFunc := func(n *gnmipb.Notification) error { + nh, data, err := parseNH(n) + switch { + case errors.Is(err, ErrNotExist): + log.Warningf("error parsing NH: %v", err) + case err != nil: + return err + default: + a.NextHops[nh] = data + } + return nil + } + cacheTraversalPaths, err := generateCacheTraversalPaths(subscriptionPaths(dut)) + if err != nil { + return nil, err + } + parsers := []struct { + paths []string + f func(n *gnmipb.Notification) error + }{ + { + paths: cacheTraversalPaths["prefix"], + f: prefixFunc, + }, + { + paths: cacheTraversalPaths["nhg"], + f: nhgFunc, + }, + { + paths: cacheTraversalPaths["nh"], + f: nhFunc, + }, + } + for _, p := range parsers { + for _, path := range p.paths { + if err := c.traverse(path, p.f); err != nil { + return nil, err + } + } + } + return a, nil +} + +// logMetadata sends cache metadata to testing log. +func (c *aftCache) logMetadata(t *testing.T, start time.Time) error { + m := c.cache.Metadata()[c.target] + msg := fmt.Sprintf("After %v: ", time.Since(start).Truncate(time.Millisecond)) + fields := []string{metadata.LeafCount, metadata.AddCount, metadata.UpdateCount, metadata.DelCount} + for _, f := range fields { + v, err := m.GetInt(f) + if err != nil { + return err + } + msg += fmt.Sprintf("%s:%d ", f, v) + } + t.Log(msg) + return nil +} + +// traverse walks over portion of cache described by the provided +// openconfig path ocPath, and calls function f on the value at each leaf. +// ocPath is a unix path style string which can contain globs (*). +func (c *aftCache) traverse(ocPath string, f func(val *gnmipb.Notification) error) error { + return c.cache.Query(c.target, strings.Split(ocPath, "/"), func(_ []string, _ *ctree.Leaf, val any) error { + n, ok := val.(*gnmipb.Notification) + if !ok { + return fmt.Errorf("value is not a notification: %v", val) + } + return f(n) + }) +} + +// ResolveRoute gets the possible next hops for a specific route. +// Uses 0 as the DSCP bits as a default. Usually this corresponds to a low priority traffic class. +// ResolveRoute should only be used in test cases where we don't expect to encounter CNHGs. +// For testing CNHGs, it's better to explicitly set DSCP with ResolveRouteCBF. +func (a *AFTData) resolveRoute(prefix string) ([]*aftNextHop, error) { + return a.resolveRouteCBF(prefix, 0) +} + +func (a *AFTData) isCNHG(nhgID uint64) (bool, error) { + // Assume we've already checked the nhgID exists. + if len(a.NextHopGroups[nhgID].NHIDs) > 0 && len(a.NextHopGroups[nhgID].Conditionals) > 0 { + return false, fmt.Errorf("the NHG has both NHs and conditionals. not clear if CNHG or leaf NHG") + } + if len(a.NextHopGroups[nhgID].Conditionals) > 0 { + return true, nil + } + // If the NHG has no NHs and no conditionals, treat it as a leaf NHG. + return false, nil +} + +// ResolveRouteCBF gets the possible next hops for a specific route. +// dscp is the DSCP bits. +func (a *AFTData) resolveRouteCBF(prefix string, dscp uint8) ([]*aftNextHop, error) { + if _, ok := a.Prefixes[prefix]; !ok { + return nil, fmt.Errorf("missing prefix. want %s, %w", prefix, ErrNotExist) + } + nhgID := a.Prefixes[prefix] + visited := map[uint64]bool{} // Track NHGs we've seen in case of circular references. + for { + if _, ok := a.NextHopGroups[nhgID]; !ok { + return nil, fmt.Errorf("missing reference for prefix %s, NHG %d not found: %w", prefix, nhgID, ErrNotExist) + } + isCNHG, err := a.isCNHG(nhgID) + if err != nil { + return nil, fmt.Errorf("error in prefix %s, error reading NHG %d: %v", prefix, nhgID, err) + } + if !isCNHG { + // This is a leaf, non-conditional NHG node. Terminate. + break + } + // We look up each ID in visited and add all IDs to visited. This should always terminate. + if _, ok := visited[nhgID]; ok { + return nil, fmt.Errorf("circular reference for prefix %s, NHG %d already seen", prefix, nhgID) + } + visited[nhgID] = true + match := false + for _, c := range a.NextHopGroups[nhgID].Conditionals { + for _, d := range c.DSCP { + if d == dscp { + if match { + // We already matched a different conditional. Undefined behavior. + return nil, fmt.Errorf("undefined behavior for prefix %s, multiple conditionals apply", prefix) + } + match = true + nhgID = c.NHGID + break + } + } + } + if !match { + return nil, nil // No conditionals matched. Return empty NH slice (nil). + } + } + var nhs []*aftNextHop + for _, nhID := range a.NextHopGroups[nhgID].NHIDs { + if _, ok := a.NextHops[nhID]; !ok { + return nil, fmt.Errorf("missing reference for prefix %s, NH %d not found, %w", prefix, nhID, ErrNotExist) + } + nhs = append(nhs, a.NextHops[nhID]) + } + return nhs, nil +} + +func (c *aftCache) addAFTNotification(n *gnmipb.SubscribeResponse) error { + if n.GetSyncResponse() { + // No-op for now. + return nil + } + + update := n.GetUpdate() + if update == nil { + return fmt.Errorf("SubscribeResponse missing Update: %v", n) + } + if update.GetPrefix() == nil { + update.Prefix = &gnmipb.Path{} + } + prefix := update.GetPrefix() + if prefix.GetOrigin() == "" { + prefix.Origin = "openconfig" + } + if prefix.GetTarget() == "" { + prefix.Target = c.target + } + err := c.cache.GnmiUpdate(update) + + if err != nil { + return err + } + return nil +} + +func newAFTCache(target string) *aftCache { + return &aftCache{ + cache: cache.New([]string{target}), + target: target, + } +} + +func newAFT() *AFTData { + return &AFTData{ + Prefixes: map[string]uint64{}, + NextHopGroups: map[uint64]*aftNextHopGroup{}, + NextHops: map[uint64]*aftNextHop{}, + } +} + +type aftSubscriptionResponse struct { + notification *gnmipb.SubscribeResponse + err error +} + +// aftSubscribe subscribes to a gNMI client and creates a channel to read from the subscription +// stream asynchronously. +func aftSubscribe(ctx context.Context, t *testing.T, c gnmipb.GNMIClient, dut *ondatra.DUTDevice) <-chan *aftSubscriptionResponse { + sub, err := c.Subscribe(ctx) + if err != nil { + t.Fatalf("error in Subscribe(): %v", err) + } + req, err := checkForRoutesRequest(dut) + if err != nil { + t.Fatalf("error preparing subscribe request: %v", err) + } + t.Logf("Sending subscribe request: %v", req) + if err := sub.Send(req); err != nil { + t.Fatalf("error sending subscribe request %v: %v", req, err) + } + + buffer := make(chan *aftSubscriptionResponse, aftBufferSize) + // Don't need to close the buffer channel. We don't need that signal, the stream stopping logic is with the consumer. + go func() { + for { + n, err := sub.Recv() + resp := &aftSubscriptionResponse{n, err} + select { + case buffer <- resp: + case <-ctx.Done(): + // Context cancellation also makes sub.Recv() return an error. We rely on the out channel's + // buffer filling up or random chance (select picks a random available case) to hit + // <-ctx.Done() and return. + return + } + } + }() + return buffer +} + +// AFTStreamSession represents a single gNMI AFT streaming session and cached AFT state. It contains +// a subscription that can be used across multiple calls to ListenUntil(). +type AFTStreamSession struct { + buffer <-chan *aftSubscriptionResponse + Cache *aftCache + start time.Time +} + +// NewAFTStreamSession constructs an AFTStreamSession. It subscribes to a given gNMI client. +func NewAFTStreamSession(ctx context.Context, t *testing.T, c gnmipb.GNMIClient, dut *ondatra.DUTDevice) *AFTStreamSession { + return &AFTStreamSession{ + buffer: aftSubscribe(ctx, t, c, dut), + Cache: newAFTCache(dut.Name()), + } +} + +// notificationHook is a function that will be called when each notification is received, before updating the AFT cache. +type notificationHook struct { + description string + notificationFunc func(c *aftCache, n *gnmipb.SubscribeResponse) error +} + +// PeriodicHook is a function that will be called on a regular interval with the current AFT cache. +type PeriodicHook struct { + Description string + PeriodicFunc func(c *aftCache) (bool, error) +} + +// loggingPeriodicHook prints AFT stats to the log on a regular interval during an AFT stream. +func loggingPeriodicHook(t *testing.T, start time.Time) PeriodicHook { + return PeriodicHook{ + Description: "Log stream stats", + PeriodicFunc: func(c *aftCache) (bool, error) { + c.logMetadata(t, start) + return false, nil + }, + } +} + +func (ss *AFTStreamSession) loggingFinal(t *testing.T) { + ss.Cache.logMetadata(t, ss.start) + t.Logf("After %v: Finished streaming.", time.Since(ss.start).Truncate(time.Millisecond)) + if len(missingPrefixes) == 0 { + return + } + filename, err := writeMissingPrefixes(missingPrefixes) + if err != nil { + t.Errorf("error writing missing prefixes: %v", err) + } else { + t.Logf("Wrote missing prefixes to %s", filename) + } +} + +// ListenUntil updates AFT with notifications from a gNMI client in streaming mode, and stops +// listening based on the stoppingCondition hook. +func (ss *AFTStreamSession) ListenUntil(ctx context.Context, t *testing.T, timeout time.Duration, stoppingCondition PeriodicHook) { + t.Helper() + ss.start = time.Now() + defer ss.loggingFinal(t) // Print stats one more time before exiting even in case of fatal error. + var pnhs []notificationHook + phs := []PeriodicHook{loggingPeriodicHook(t, ss.start), stoppingCondition} + ss.listenUntil(ctx, t, timeout, pnhs, phs) +} + +func (ss *AFTStreamSession) listenUntil(ctx context.Context, t *testing.T, timeout time.Duration, preUpdateHooks []notificationHook, periodicHooks []PeriodicHook) { + t.Helper() + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + periodicTicker := time.NewTicker(periodicInterval) + for { + select { + case resp := <-ss.buffer: + if resp.err != nil { + // Context cancellation can hit this code path from the stream sending a context cancellation error. + t.Fatalf("error from gNMI stream: %v", resp.err) + } + + for _, hook := range preUpdateHooks { + err := hook.notificationFunc(ss.Cache, resp.notification) + if err != nil { + t.Fatalf("error in notificationHook %q: %v", hook.description, err) + } + } + err := ss.Cache.addAFTNotification(resp.notification) + switch { + case errors.Is(err, cache.ErrStale): + case err != nil: + t.Fatalf("error updating AFT cache with response %v: %v", resp.notification, err) + } + case <-periodicTicker.C: + s := time.Now() + for _, hook := range periodicHooks { + done, err := hook.PeriodicFunc(ss.Cache) + if err != nil { + t.Fatalf("error in PeriodicHook %q: %v", hook.Description, err) + } + if done { + return + } + } + d := time.Since(s) + if d > periodicDeadline { + // If periodic hooks take too long, we can't guarantee we can catch up on processing + // notifications and the test may slow down artificially. The periodic hooks should be + // optimized or the parameters (periodicInterval, periodicDeadline) should be tuned. + t.Fatalf("periodic hooks took %v, exceeding deadline of %v", d.Truncate(time.Millisecond), periodicDeadline) + } + case <-ctx.Done(): + t.Fatalf("context cancelled: %v", ctx.Err()) + return + } + } +} + +// DeletionStoppingCondition returns a PeriodicHook which can be used to check if all given prefixes have been deleted. +func DeletionStoppingCondition(t *testing.T, dut *ondatra.DUTDevice, wantDeletePrefixes map[string]bool) PeriodicHook { + return PeriodicHook{ + Description: "Route delete stopping condition", + PeriodicFunc: func(c *aftCache) (bool, error) { + a, err := c.ToAFT(dut) + if err != nil { + return false, err + } + gotPrefixes := a.Prefixes + nRem := 0 + for p := range gotPrefixes { + if _, ok := wantDeletePrefixes[p]; ok { + nRem++ + } + } + t.Logf("Got %d deleted prefixes out of %d wanted prefixes to delete so far.", len(wantDeletePrefixes)-nRem, len(wantDeletePrefixes)) + if nRem > 0 { + return false, nil + } + t.Logf("Finished checking for deleted routes: %s", time.Now().String()) + return true, nil + }, + } +} + +// InitialSyncStoppingCondition returns a PeriodicHook which can be used to check if all wanted prefixes have been received with given next hop IP addresses. +func InitialSyncStoppingCondition(t *testing.T, dut *ondatra.DUTDevice, wantPrefixes, wantIPV4NHs, wantIPV6NHs map[string]bool) PeriodicHook { + nhFailCount := 0 + const nhFailLimit = 20 + logDuration := func(start time.Time, stage string) { + t.Logf("InitialSyncStoppingCondition: Stage: %s took %.2f seconds", stage, time.Since(start).Seconds()) + } + return PeriodicHook{ + Description: "Initial sync stopping condition", + PeriodicFunc: func(c *aftCache) (bool, error) { + start := time.Now() + a, err := c.ToAFT(dut) + logDuration(start, "Convert cache to AFT") + if err != nil { + return false, err + } + + // Check prefixes. + checkPrefixStart := time.Now() + gotPrefixes := a.Prefixes + nPrefixes := len(wantPrefixes) + nGot := 0 + missingPrefixes := map[string]bool{} + for p := range wantPrefixes { + if _, ok := gotPrefixes[p]; ok { + nGot++ + } else { + missingPrefixes[p] = true + } + } + t.Logf("Got %d out of %d wanted prefixes so far.", nGot, nPrefixes) + logDuration(checkPrefixStart, "Check Prefixes") + if nGot < nPrefixes { + t.Logf("%d missing prefixes\n", len(missingPrefixes)) + return false, nil + } + + // Check next hops. + checkNHStart := time.Now() + nCorrect := 0 + diffs := map[string]int{} + for p := range wantPrefixes { + resolved, err := a.resolveRoute(p) + got := map[string]bool{} + switch { + // Skip the check if NH is not found, retry on next periodic hook. Report missing NHs after timeout. + case errors.Is(err, ErrNotExist): + case err != nil: + return false, fmt.Errorf("error resolving next hops for prefix %v: %w", p, err) + default: + for _, r := range resolved { + got[r.IP] = true + } + } + want := wantIPV4NHs + if strings.Contains(p, ":") { + want = wantIPV6NHs + } + diff := cmp.Diff(want, got) + if diff == "" { + nCorrect++ + continue + } + if _, ok := diffs[diff]; !ok { + diffs[diff] = 0 + } + diffs[diff]++ + } + for k, v := range diffs { + t.Logf("%d mismatches of (-want +got):\n%s", v, k) + } + t.Logf("Got %d of %d correct NH so far.", nCorrect, nPrefixes) + logDuration(checkNHStart, "Check Next Hops") + if nCorrect != nPrefixes { + nhFailCount++ + if nhFailCount == nhFailLimit { + return false, fmt.Errorf("after %d tries, next hop validation still fails", nhFailLimit) + } + return false, nil + } + t.Logf("Initial sync stopping condition took %.2f sec", time.Since(start).Seconds()) + return true, nil + }, + } +} + +// parseNH parses AFT NH notification and returns NH, IP, and LSP information. +func parseNH(n *gnmipb.Notification) (uint64, *aftNextHop, error) { + e := n.GetPrefix().GetElem() + if len(e) < 5 { + return 0, nil, fmt.Errorf("not enough elements in prefix. Notification: %v", n) + } + val, ok := e[4].GetKey()["index"] + if !ok { + return 0, nil, fmt.Errorf("\"index\" not a key in element. Notification: %v", n) + } + nhID, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return 0, nil, err + } + entries := map[uint64]bool{nhID: true} + var path string + // Loop over updates, looking for all instances of next hop. + updates := schema.NotificationToPoints(n) + for _, u := range updates { + path, err = ygot.PathToSchemaPath(u.Path) + switch { + case err != nil: + return 0, nil, err + case strings.HasSuffix(path, "state/index"): + entries[u.Val.GetUintVal()] = true + } + } + // Ensure all next-hop entries are consistent. + if len(entries) != 1 { + return 0, nil, fmt.Errorf("the NH values do not match between Prefix and Update parts of message. Notification: %v", n) + } + // Loop over update, looking for ip-address, lsp-name, and/or interface name. + found := false + nh := &aftNextHop{} + for _, u := range updates { + path, err = ygot.PathToSchemaPath(u.Path) + switch { + case err != nil: + return 0, nil, err + case strings.HasSuffix(path, "state/ip-address"): + nh.IP = u.Val.GetStringVal() + found = true + case strings.HasSuffix(path, "state/lsp-name"): + nh.LSPName = u.Val.GetStringVal() + found = true + case strings.HasSuffix(path, "interface-ref/state/interface"): + nh.IntfName = u.Val.GetStringVal() + found = true + } + } + if !found { + err = fmt.Errorf("ip-address, interface, nor lsp-name were found in notification %v. %w", n, ErrNotExist) + } + return nhID, nh, err +} + +// parseNHG parses AFT NHG notification and return NHG and next hops from the notification. +func parseNHG(n *gnmipb.Notification) (uint64, *aftNextHopGroup, error) { + e := n.GetPrefix().GetElem() + if len(e) < 5 { + return 0, nil, fmt.Errorf("not enough elements in prefix. Notification: %v", n) + } + val, ok := e[4].GetKey()["id"] + if !ok { + return 0, nil, fmt.Errorf("\"id\" not a key in element. Notification: %v", n) + } + nhgID, err := strconv.ParseUint(val, 10, 64) + if err != nil { + return 0, nil, err + } + var p string + var updates []schema.Point + entries := map[uint64]bool{nhgID: true} + + // Loop over updates, looking for instances of next hop groups and next hops. + updates = schema.NotificationToPoints(n) + + nhg := &aftNextHopGroup{ + NHIDs: []uint64{}, + NHWeights: map[uint64]uint64{}, + } + for _, u := range updates { + p, err = ygot.PathToSchemaPath(u.Path) + if strings.HasPrefix(p, nextHopGroupConditionPath) { + // Ignore conditional next hops groups for now. + return 0, nil, fmt.Errorf("next hop group notification is conditional:%v, %w", n, ErrUnsupported) + } + switch { + case err != nil: + return 0, nil, err + // Match for the path of the form: + // /network-instances/network-instance/DEFAULT/afts/next-hop-groups/next-hop-group[id=]/state/id + case strings.HasSuffix(p, "state/id"): + entries[u.Val.GetUintVal()] = true + // Match for the path of the form: + // /network-instances/network-instance/DEFAULT/afts/next-hop-groups/next-hop-group[id=]/state/index + case strings.HasSuffix(p, "state/index"): + nhg.NHIDs = append(nhg.NHIDs, u.Val.GetUintVal()) + case p == nextHopWeightPath: + nhID, err := strconv.ParseUint(u.Path.GetElem()[6].GetKey()["index"], 10, 64) + if err != nil { + return 0, nil, err + } + nhg.NHWeights[nhID] = u.Val.GetUintVal() + } + } + if len(nhg.NHIDs) == 0 { + log.Warningf("no next hop values were found in notification %v, %v", n, ErrNotExist) + } + if len(entries) != 1 { + err = fmt.Errorf("the NHG values do not match between Prefix and Update parts of message. Notification: %v, %w", n, err) + } + if len(nhg.NHIDs) != len(nhg.NHWeights) { + err = fmt.Errorf("missing Weights for a few NHIDs. Notification: %v, %w", n, err) + } + return nhgID, nhg, err +} + +// parsePrefix extracts the IP prefix and next-hop-group ID from an AFT prefix GNMI notification. +func parsePrefix(n *gnmipb.Notification) (string, uint64, error) { + // Normalizes paths for the "updates" in the gNMI notification. + updates := schema.NotificationToPoints(n) + if len(updates) == 0 { + return "", 0, fmt.Errorf("missing updates") + } + e := updates[0].Path.GetElem() + if len(e) < 5 { + return "", 0, fmt.Errorf("invalid prefix path in Notification: %v", n) + } + prefix, ok := updates[0].Path.GetElem()[4].GetKey()["prefix"] + if !ok { + return "", 0, fmt.Errorf("invalid prefix path") + } + wantFields := map[string]bool{} + nhgID := uint64(0) + for _, u := range updates { + path, err := ygot.PathToSchemaPath(u.Path) + if err != nil { + return "", 0, fmt.Errorf("error converting path to schema path: %v", err) + } + switch { + case path == prefixNHGPathV4 || path == prefixNHGPathV6: + wantFields[path] = true + nhgID = u.Val.GetUintVal() + case path == prefixPathV4 || path == prefixPathV6: + wantFields[path] = true + if u.Val.GetStringVal() != prefix { + return "", 0, fmt.Errorf("prefix mismatch") + } + // known unused paths + case slices.Contains(unusedPaths, path): + default: + log.Warningf("unexpected path %q in prefix notification %v", path, n) + } + } + if len(wantFields) < 2 { + return "", 0, fmt.Errorf("missing required fields %v from the response %v", wantFields, n) + } + return prefix, nhgID, nil +} + +func checkForRoutesRequest(dut *ondatra.DUTDevice) (*gnmipb.SubscribeRequest, error) { + subReq := &gnmipb.SubscribeRequest_Subscribe{ + Subscribe: &gnmipb.SubscriptionList{ + Mode: gnmipb.SubscriptionList_STREAM, + Prefix: &gnmipb.Path{Origin: "openconfig", Target: dut.Name()}, + Encoding: gnmipb.Encoding_PROTO, + }, + } + for _, paths := range subscriptionPaths(dut) { + for _, p := range paths { + pp, err := ygot.StringToPath(p, ygot.StructuredPath) + if err != nil { + return nil, fmt.Errorf("failed to parse path: %v", err) + } + subReq.Subscribe.Subscription = append(subReq.Subscribe.Subscription, &gnmipb.Subscription{Path: pp}) + } + } + return &gnmipb.SubscribeRequest{Request: subReq}, nil +} + +func writeMissingPrefixes(missingPrefixes map[string]bool) (string, error) { + absFilename, err := filepath.Abs(missingPrefixesFile) + if err != nil { + return "", err + } + f, err := os.OpenFile(absFilename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) + if err != nil { + return "", err + } + defer f.Close() + for p := range missingPrefixes { + if _, err := fmt.Fprintln(f, p); err != nil { + return "", err + } + } + return absFilename, nil +} diff --git a/internal/telemetry/schema/schema.go b/internal/telemetry/schema/schema.go new file mode 100644 index 00000000000..2bd161d1afc --- /dev/null +++ b/internal/telemetry/schema/schema.go @@ -0,0 +1,64 @@ +// Copyright 2025 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 schema provides utilities for interacting with gNMI Notifications +// and the OpenConfig schema for those Notifications. +// +// The package offers a function to normalize gNMI notifications into +// slices of Paths. Each Path contains a fully specified gNMI path, +// and will contain a single updated value, instead of the compressed +// representation in Notifications, where there are multiple updates +// per Notification, and the path is split between the prefix and the Updates. +// +// To use this library, get a slice of Notifications, convert them to Paths, +// then call the various helper libraries on each Path to validate the data. +package schema + +import ( + gnmipb "github.com/openconfig/gnmi/proto/gnmi" +) + +// Point stores data about a single gNMI/OpenConfig data point. +// This is the equivalent of a gNMI Update, though the path is +// fully normalized, i.e. Point.Path should contain the elements from both +// the gnmi.Notification prefix and the Update.Path concatenated together. +type Point struct { + // Path is the complete gNMI path for the data point. + Path *gnmipb.Path + // Val is the value of the data point, called "Val" for consistency with + // the gNMI protobuf, where "Value" is a legacy type. + Val *gnmipb.TypedValue +} + +// NotificationToPoints extracts all of the gnmi.Updates from the Notification, +// concatenates the prefix and Update path, then creates a new Path +// for each new update. +// +// Note that we only accept TypedValue values. +func NotificationToPoints(n *gnmipb.Notification) []Point { + if n == nil { + return []Point{} + } + points := make([]Point, 0, len(n.GetUpdate())) + for _, u := range n.GetUpdate() { + fullPath := &gnmipb.Path{} + fullPath.Elem = append(fullPath.GetElem(), n.GetPrefix().GetElem()...) + fullPath.Elem = append(fullPath.GetElem(), u.GetPath().GetElem()...) + points = append(points, Point{ + Path: fullPath, + Val: u.GetVal(), + }) + } + return points +} diff --git a/proto/deviations.proto b/proto/deviations.proto new file mode 100644 index 00000000000..284f38336d4 --- /dev/null +++ b/proto/deviations.proto @@ -0,0 +1,94 @@ +// Copyright 2025 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 +// +// https://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. + +// deviations.proto defines the protocol buffer messages required to manage the +// lifecycle of deviations used in featureprofiles. + +syntax = "proto3"; + +package openconfig.deviations; + +import "github.com/openconfig/featureprofiles/proto/metadata.proto"; +import "github.com/openconfig/featureprofiles/proto/ocpaths.proto"; +import "github.com/openconfig/gnmi/proto/gnmi/gnmi.proto"; + +option go_package = "github.com/openconfig/featureprofiles/proto/deviations_go_proto;deviations"; + +// DeviationType specifies the type of the deviation. +enum DeviationType { + // DEVIATION_TYPE_UNSPECIFIED indicates that the deviation type is not + // specified. + DEVIATION_TYPE_UNSPECIFIED = 0; + // DEVIATION_TYPE_PATH indicates that the deviation impacts a particular path, + // which is then omitted and replaced with the contents of the deviation. + DEVIATION_TYPE_PATH = 1; + // DEVIATION_TYPE_VALUE indicates that the deviation impacts a particular + // path, which continues to be used but its value is changed. + DEVIATION_TYPE_VALUE = 2; + // DEVIATION_TYPE_CLI indicates that the deviation impacts a particular path + // which is then omitted and replaced with the contents of the deviation. + DEVIATION_TYPE_CLI = 3; +} + +// DeviationRegistry contains a list of deviations. +message DeviationRegistry { + repeated Deviation deviations = 1; +} + +// Deviation specifies a single deviation. +message Deviation { + // Name of the deviation. + string name = 1; + // Type of the deviation. + DeviationType type = 2; + // List of paths that are impacted by the deviation. + openconfig.ocpaths.OCPaths impacted_paths = 3; + // List of platforms for which the deviation is applicable. + repeated PlatformData platforms = 4; +} + +// PlatformData comprises of the platform for which the deviation is applicable +// along with the issue_url tracking the deviation. +message PlatformData { + // issue_url is the URL for the issue tracking the deviation. + string issue_url = 1; + // platform is the platform for which the deviation is applicable. + // Missing value of hardware_model_regex implies that the deviation is + // hardware agnostic. + openconfig.testing.Metadata.Platform platform = 2; + // DeviationValues specifies the canonical and vendor specific values for a + // deviation. + message DeviationValues { + // OC standard value for the deviation. + gnmi.TypedValue oc_standard_value = 1; + // Vendor specific value for the deviation. + gnmi.TypedValue vendor_specific_value = 2; + } + // CliCommands specifies the CLI commands for a deviation. + message CliCommands { + // List of CLI commands. Each command is a regex to match cli command + // format. + repeated string commands = 1; + } + // deviation_field specifies the additional paths, CLI commands or deviation + // values. + oneof deviation_field { + // List of additional paths for the deviation. + openconfig.ocpaths.OCPaths additional_paths = 3; + // List of CLI commands for the deviation. + CliCommands clis = 4; + // Canonical and vendor specific values for the deviation. + DeviationValues deviation_values = 5; + } +} diff --git a/proto/feature.proto b/proto/feature.proto index 265b02c391f..e40315b7a3d 100644 --- a/proto/feature.proto +++ b/proto/feature.proto @@ -47,63 +47,3 @@ message FeatureProfileID { // Version number for this instantiation of the profile. optional int32 version = 2; } - -message TelemetryPath { - optional string path = 1; -} - -message ConfigPath { - optional string path = 1; -} - -message GNOIService { - // Service name for the GNOI endpoint - // - // Example: gnoi.bgp.BGP - optional string service_name = 1; - - // Method name for GNOI endpoint - // - // Example: ClearBGPNeighbor - optional string method_name = 2; -} - -// GNMIService describes compliance with the gNMI service. -message GNMIService { - // method_name specifies the name of the method that is - // being tested. The methods are from the gnmi service (e.g., Get). - enum Method { - MD_INVALID = 0; - MD_GET = 1; // gNMI.Get - MD_CAPABILITIES = 2; // gNMI.Capabilities - MD_SUBSCRIBE = 3; // gNMI.Subscribe - MD_SET = 4; // gNMI.Set - } - - // The method that is covered within the gNMI protocol. - optional Method method_name = 2; - - // TODO(robjs): for some RPCs, like Subscribe, we may want - // further description of modes (e.g., POLL, STREAM, ONCE). -} - -message FeatureProfile { - // Unique identifier for the service profile. - optional FeatureProfileID id = 1; - - // A list of configuration and state paths that compose the feature profile. - repeated ConfigPath config_path = 2; - - // A list of configuration and state paths that compose the feature profile. - repeated TelemetryPath telemetry_path = 3; - - // A list of feature profiles this feature profile depends on. For example, - // A iBGP feature profile might depend on the BGP base feature profile. - repeated FeatureProfileID feature_profile_dependency = 4; - - // A list of GNOI operational commands that compose the feature profile. - repeated GNOIService gnoi_service = 5; - - // A list of GNMI methods that compose the feature profile. - repeated GNMIService gnmi_service = 6; -} diff --git a/proto/metadata.proto b/proto/metadata.proto index ab524155dbc..edc3f475575 100644 --- a/proto/metadata.proto +++ b/proto/metadata.proto @@ -42,6 +42,8 @@ message Metadata { TESTBED_DUT_400ZR_100G_4LINKS = 10; TESTBED_DUT_400FR_100G_4LINKS = 11; TESTBED_DUT_ATE_5LINKS = 12; + TESTBED_DUT_800ZR = 13; + TESTBED_DUT_800ZR_PLUS = 14; } // Testbed on which the test is intended to run. Testbed testbed = 4; @@ -626,9 +628,6 @@ message Metadata { // CISCO: b/376241033 // CISCO: b/340859662 bool enable_multipath_under_afi_safi = 230; - // Device have different default value for allow own as. - // Juniper : b/373559004 - bool bgp_allowownas_diff_default_value = 231; // Cisco numbering for OTN channel assignment starts from 1 instead of 0 bool otn_channel_assignment_cisco_numbering = 232; // Cisco pre-fec-ber inactive value for CISCO-ACACIA vendors @@ -719,6 +718,7 @@ message Metadata { // Devices that block one IS-IS level specific authentication config attribute for P2P links. // The same leafs can be set directly under ISIS Interface authentication /network-instances/network-instance/protocols/protocol/isis/interfaces/interface/authentication. bool set_isis_auth_with_interface_authentication_container = 261; + // Devices that do not support GRE/GUE tunnel interface oc. // Juniper b/398171114 bool gre_gue_tunnel_interface_oc_unsupported = 262; @@ -759,8 +759,178 @@ message Metadata { // Cisco b/414333771 bool bgp_set_med_action_unsupported = 272; + // Devices that do not support next-hop-group config + // Arista b/390507957 + bool next_hop_group_config_unsupported = 273; + + // Arista b/390507780 + bool qos_shaper_config_unsupported = 274; + + // Arista b/390507780 + bool qos_shaper_state_unsupported = 275; + + // Arista b/393178770 + bool ethernet_over_mplsogre_unsupported = 276; + + // Arista b/390507408 + bool sflow_unsupported = 277; + + // Arista b/390507402 + bool mpls_unsupported = 278; + + // Arista b/390507399 + bool macsec_unsupported = 279; + + // Arista b/390506900 + bool gue_gre_decap_unsupported = 280; + + // Arista b/390506584 + bool mpls_label_classification_unsupported = 281; + + // Arista b/390506395 + bool local_proxy_unsupported = 282; + + // Arista b/390506513 + bool static_mpls_unsupported = 283; + + // Arista b/390504878 + bool qos_classification_unsupported = 284; + + // Arista b/390503348 + bool policy_forwarding_unsupported = 285; + + // Arista b/393177745 + bool cfm_unsupported = 286; + + // Arista b/390506903 + bool label_range_unsupported = 287; + + // Arista b/390506907 + bool static_arp_unsupported = 288; + + // Arista b/390506907 + bool interface_policy_forwarding_unsupported = 289; + + // UseOldOCPathStaticLspNh for devices that do not support the new OC path for static lsp next-hops + // issues/404301960 + bool use_old_oc_path_static_lsp_nh = 290; + + // Create/Replace config leaf required + // Juniper b/419536104 + bool config_leaf_create_required = 291; + + // SkipInterfaceNameCheck is set to true for devices that do not support + // interface name check in AFT. + bool skip_interface_name_check = 292; + + // Arista b/426375784 + // FNT only issue, non-breakout ports have breakout config + bool fr_breakout_fix = 293; + + // Cisco b/421356455 + // numPhysicalChannels is not supported + bool num_physical_channels_unsupported = 294; + + // UnsupportedQoSOutputServicePolicy for devices that do not support qos output service-policy + bool unsupported_qos_output_service_policy = 295; + + // InterfaceOutputQueueNonStandardName for devices with non-standard output queue names + bool interface_output_queue_non_standard_name = 296; + + // MplsExpIngressClassifierUnsupported for devices that do not support ingress mpls exp field classification + bool mpls_exp_ingress_classifier_oc_unsupported = 297; + + // Devices that do not propagate IGP metric through redistribution + bool default_no_igp_metric_propagation = 298; + + // Skip setting send-community-type in bgp peer-group config + bool skip_bgp_peer_group_send_community_type = 299; + + // Devices that does have different AS path prepend order. + // juniper : b/425632068 + bool bgp_as_path_prepend_order_mismtach = 300; + + // Devices that need explicit swap_src_dst_mac set with loopback_mode + // Nokia b/430183279 + bool explicit_swap_src_dst_mac_needed_for_loopback_mode = 301; + + // link_local_instead_of_nh is set to true for devices that give + // link-local address instead of NH in AFT. + bool link_local_instead_of_nh = 302; + + // low_scale_aft returns true if device requires low scale AFT. + bool low_scale_aft = 303; + + // Devices that do not support system-description config path + // Nokia b/431929861 + bool missing_system_description_config_path = 304; + + // Juniper b/428613305 + // FEC uncorrectable errors accumulate over time and are not cleared unless the component is reset on target + bool non_interval_fec_error_counter = 305; + + // Device does not support ntp source address + bool ntp_source_address_unsupported = 306; + + // Devices does not support static mpls lsp + bool static_mpls_lsp_oc_unsupported = 307; + + // Device doesnot support gre enacapsulation + bool gre_decapsulation_oc_unsupported = 308; + + // SRGB and SLGB config through OC is not reflecting + bool isis_srgb_srlb_unsupported = 309; + + // Prefix segment configuration not supported + bool isis_sr_prefix_segment_config_unsupported = 310; + + // node segment configuration not supported + bool isis_sr_node_segment_config_unsupported = 311; + + // Devices that do not support policy forwarding on next-hop + bool policy_forwarding_to_next_hop_oc_unsupported = 312; + // Cisco: b/402672689 + // Devices that support sflow ingress min sampling rate of 1/N with N<1,000,000 can use + // this deviation to set specific value of N. Returns N=1,000,000 by default. + uint32 sflow_ingress_min_sampling_rate = 313; + + // DUT not supporting with qos remarking + // Arista: b/415889077 + bool qos_remark_oc_unsupported = 314; + + // Devices that do not support policy forwarding encapsulate gre action + // Arista: b/409347274 + bool policy_forwarding_gre_encapsulation_oc_unsupported = 315; + + // policy rule based counters unsupported + // Arista : https://partnerissuetracker.corp.google.com/issues/425628787 + bool policy_rule_counters_oc_unsupported = 316; + + // Devices that must have OTN to ETH assignment. + // Arista : https://partnerissuetracker.corp.google.com/issues/434922681 + bool otn_to_eth_assignment = 317; + + // Devices that do not support import export policies configured in network instance + bool network_instance_import_export_policy_oc_unsuppored = 318; + + // Device does not support 'origin' field in gNMI/gNOI RPC paths. + // Arista: https://partnerissuetracker.corp.google.com/issues/439656904 + bool skip_origin = 319; + + // Devices that support pre-defined max ecmp paths + // Juniper b/422688435 + bool predefined_max_ecmp_paths = 320; + + // b/425503156 + // Device does not support decapsulation group + bool decapsulate_gue_oc_unsupported = 321; + + // Device does not support line-port configuration on optical channel + // components for Nokia and Arista. + bool line_port_unsupported = 322; + // Reserved field numbers and identifiers. - reserved 84, 9, 28, 20, 38, 43, 90, 97, 55, 89, 19, 36, 35, 40, 113, 131, 141, 173, 234, 254; + reserved 84, 9, 28, 20, 38, 43, 90, 97, 55, 89, 19, 36, 35, 40, 113, 131, 141, 173, 234, 254, 231; } message PlatformExceptions { @@ -787,4 +957,4 @@ message Metadata { // Whether this test only checks paths for presence rather than semantic // checks. bool path_presence_test = 7; -} +} \ No newline at end of file diff --git a/proto/metadata_go_proto/metadata.pb.go b/proto/metadata_go_proto/metadata.pb.go index 45b624101d5..53942e9e547 100644 --- a/proto/metadata_go_proto/metadata.pb.go +++ b/proto/metadata_go_proto/metadata.pb.go @@ -14,8 +14,8 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 -// protoc v5.28.1 +// protoc-gen-go v1.36.8 +// protoc v3.21.12 // source: metadata.proto package metadata_go_proto @@ -23,6 +23,7 @@ package metadata_go_proto import ( reflect "reflect" sync "sync" + unsafe "unsafe" proto "github.com/openconfig/ondatra/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -53,6 +54,8 @@ const ( Metadata_TESTBED_DUT_400ZR_100G_4LINKS Metadata_Testbed = 10 Metadata_TESTBED_DUT_400FR_100G_4LINKS Metadata_Testbed = 11 Metadata_TESTBED_DUT_ATE_5LINKS Metadata_Testbed = 12 + Metadata_TESTBED_DUT_800ZR Metadata_Testbed = 13 + Metadata_TESTBED_DUT_800ZR_PLUS Metadata_Testbed = 14 ) // Enum value maps for Metadata_Testbed. @@ -71,6 +74,8 @@ var ( 10: "TESTBED_DUT_400ZR_100G_4LINKS", 11: "TESTBED_DUT_400FR_100G_4LINKS", 12: "TESTBED_DUT_ATE_5LINKS", + 13: "TESTBED_DUT_800ZR", + 14: "TESTBED_DUT_800ZR_PLUS", } Metadata_Testbed_value = map[string]int32{ "TESTBED_UNSPECIFIED": 0, @@ -86,6 +91,8 @@ var ( "TESTBED_DUT_400ZR_100G_4LINKS": 10, "TESTBED_DUT_400FR_100G_4LINKS": 11, "TESTBED_DUT_ATE_5LINKS": 12, + "TESTBED_DUT_800ZR": 13, + "TESTBED_DUT_800ZR_PLUS": 14, } ) @@ -173,10 +180,7 @@ func (Metadata_Tags) EnumDescriptor() ([]byte, []int) { // Metadata about a Feature Profiles test. type Metadata struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // UUID of the test. Uuid string `protobuf:"bytes,1,opt,name=uuid,proto3" json:"uuid,omitempty"` // ID of the test in the test plan. @@ -194,15 +198,15 @@ type Metadata struct { // Whether this test only checks paths for presence rather than semantic // checks. PathPresenceTest bool `protobuf:"varint,7,opt,name=path_presence_test,json=pathPresenceTest,proto3" json:"path_presence_test,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Metadata) Reset() { *x = Metadata{} - if protoimpl.UnsafeEnabled { - mi := &file_metadata_proto_msgTypes[0] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_metadata_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Metadata) String() string { @@ -213,7 +217,7 @@ func (*Metadata) ProtoMessage() {} func (x *Metadata) ProtoReflect() protoreflect.Message { mi := &file_metadata_proto_msgTypes[0] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -278,10 +282,7 @@ func (x *Metadata) GetPathPresenceTest() bool { } type Metadata_Platform struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Vendor of the device. Vendor proto.Device_Vendor `protobuf:"varint,1,opt,name=vendor,proto3,enum=ondatra.Device_Vendor" json:"vendor,omitempty"` // Regex for hardware model of the device. @@ -290,15 +291,15 @@ type Metadata_Platform struct { // Regex for software version of the device. // The empty string will match any software version. SoftwareVersionRegex string `protobuf:"bytes,4,opt,name=software_version_regex,json=softwareVersionRegex,proto3" json:"software_version_regex,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Metadata_Platform) Reset() { *x = Metadata_Platform{} - if protoimpl.UnsafeEnabled { - mi := &file_metadata_proto_msgTypes[1] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_metadata_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Metadata_Platform) String() string { @@ -309,7 +310,7 @@ func (*Metadata_Platform) ProtoMessage() {} func (x *Metadata_Platform) ProtoReflect() protoreflect.Message { mi := &file_metadata_proto_msgTypes[1] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -346,10 +347,7 @@ func (x *Metadata_Platform) GetSoftwareVersionRegex() string { } type Metadata_Deviations struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache - unknownFields protoimpl.UnknownFields - + state protoimpl.MessageState `protogen:"open.v1"` // Device does not support interface/ipv4/enabled, // so suppress configuring this leaf. Ipv4MissingEnabled bool `protobuf:"varint,1,opt,name=ipv4_missing_enabled,json=ipv4MissingEnabled,proto3" json:"ipv4_missing_enabled,omitempty"` @@ -915,9 +913,6 @@ type Metadata_Deviations struct { // CISCO: b/376241033 // CISCO: b/340859662 EnableMultipathUnderAfiSafi bool `protobuf:"varint,230,opt,name=enable_multipath_under_afi_safi,json=enableMultipathUnderAfiSafi,proto3" json:"enable_multipath_under_afi_safi,omitempty"` - // Device have different default value for allow own as. - // Juniper : b/373559004 - BgpAllowownasDiffDefaultValue bool `protobuf:"varint,231,opt,name=bgp_allowownas_diff_default_value,json=bgpAllowownasDiffDefaultValue,proto3" json:"bgp_allowownas_diff_default_value,omitempty"` // Cisco numbering for OTN channel assignment starts from 1 instead of 0 OtnChannelAssignmentCiscoNumbering bool `protobuf:"varint,232,opt,name=otn_channel_assignment_cisco_numbering,json=otnChannelAssignmentCiscoNumbering,proto3" json:"otn_channel_assignment_cisco_numbering,omitempty"` // Cisco pre-fec-ber inactive value for CISCO-ACACIA vendors @@ -1025,15 +1020,136 @@ type Metadata_Deviations struct { // Set-Med-Action is not supported for BGP // Cisco b/414333771 BgpSetMedActionUnsupported bool `protobuf:"varint,272,opt,name=bgp_set_med_action_unsupported,json=bgpSetMedActionUnsupported,proto3" json:"bgp_set_med_action_unsupported,omitempty"` + // Devices that do not support next-hop-group config + // Arista b/390507957 + NextHopGroupConfigUnsupported bool `protobuf:"varint,273,opt,name=next_hop_group_config_unsupported,json=nextHopGroupConfigUnsupported,proto3" json:"next_hop_group_config_unsupported,omitempty"` + // Arista b/390507780 + QosShaperConfigUnsupported bool `protobuf:"varint,274,opt,name=qos_shaper_config_unsupported,json=qosShaperConfigUnsupported,proto3" json:"qos_shaper_config_unsupported,omitempty"` + // Arista b/390507780 + QosShaperStateUnsupported bool `protobuf:"varint,275,opt,name=qos_shaper_state_unsupported,json=qosShaperStateUnsupported,proto3" json:"qos_shaper_state_unsupported,omitempty"` + // Arista b/393178770 + EthernetOverMplsogreUnsupported bool `protobuf:"varint,276,opt,name=ethernet_over_mplsogre_unsupported,json=ethernetOverMplsogreUnsupported,proto3" json:"ethernet_over_mplsogre_unsupported,omitempty"` + // Arista b/390507408 + SflowUnsupported bool `protobuf:"varint,277,opt,name=sflow_unsupported,json=sflowUnsupported,proto3" json:"sflow_unsupported,omitempty"` + // Arista b/390507402 + MplsUnsupported bool `protobuf:"varint,278,opt,name=mpls_unsupported,json=mplsUnsupported,proto3" json:"mpls_unsupported,omitempty"` + // Arista b/390507399 + MacsecUnsupported bool `protobuf:"varint,279,opt,name=macsec_unsupported,json=macsecUnsupported,proto3" json:"macsec_unsupported,omitempty"` + // Arista b/390506900 + GueGreDecapUnsupported bool `protobuf:"varint,280,opt,name=gue_gre_decap_unsupported,json=gueGreDecapUnsupported,proto3" json:"gue_gre_decap_unsupported,omitempty"` + // Arista b/390506584 + MplsLabelClassificationUnsupported bool `protobuf:"varint,281,opt,name=mpls_label_classification_unsupported,json=mplsLabelClassificationUnsupported,proto3" json:"mpls_label_classification_unsupported,omitempty"` + // Arista b/390506395 + LocalProxyUnsupported bool `protobuf:"varint,282,opt,name=local_proxy_unsupported,json=localProxyUnsupported,proto3" json:"local_proxy_unsupported,omitempty"` + // Arista b/390506513 + StaticMplsUnsupported bool `protobuf:"varint,283,opt,name=static_mpls_unsupported,json=staticMplsUnsupported,proto3" json:"static_mpls_unsupported,omitempty"` + // Arista b/390504878 + QosClassificationUnsupported bool `protobuf:"varint,284,opt,name=qos_classification_unsupported,json=qosClassificationUnsupported,proto3" json:"qos_classification_unsupported,omitempty"` + // Arista b/390503348 + PolicyForwardingUnsupported bool `protobuf:"varint,285,opt,name=policy_forwarding_unsupported,json=policyForwardingUnsupported,proto3" json:"policy_forwarding_unsupported,omitempty"` + // Arista b/393177745 + CfmUnsupported bool `protobuf:"varint,286,opt,name=cfm_unsupported,json=cfmUnsupported,proto3" json:"cfm_unsupported,omitempty"` + // Arista b/390506903 + LabelRangeUnsupported bool `protobuf:"varint,287,opt,name=label_range_unsupported,json=labelRangeUnsupported,proto3" json:"label_range_unsupported,omitempty"` + // Arista b/390506907 + StaticArpUnsupported bool `protobuf:"varint,288,opt,name=static_arp_unsupported,json=staticArpUnsupported,proto3" json:"static_arp_unsupported,omitempty"` + // Arista b/390506907 + InterfacePolicyForwardingUnsupported bool `protobuf:"varint,289,opt,name=interface_policy_forwarding_unsupported,json=interfacePolicyForwardingUnsupported,proto3" json:"interface_policy_forwarding_unsupported,omitempty"` + // UseOldOCPathStaticLspNh for devices that do not support the new OC path for static lsp next-hops + // issues/404301960 + UseOldOcPathStaticLspNh bool `protobuf:"varint,290,opt,name=use_old_oc_path_static_lsp_nh,json=useOldOcPathStaticLspNh,proto3" json:"use_old_oc_path_static_lsp_nh,omitempty"` + // Create/Replace config leaf required + // Juniper b/419536104 + ConfigLeafCreateRequired bool `protobuf:"varint,291,opt,name=config_leaf_create_required,json=configLeafCreateRequired,proto3" json:"config_leaf_create_required,omitempty"` + // SkipInterfaceNameCheck is set to true for devices that do not support + // interface name check in AFT. + SkipInterfaceNameCheck bool `protobuf:"varint,292,opt,name=skip_interface_name_check,json=skipInterfaceNameCheck,proto3" json:"skip_interface_name_check,omitempty"` + // Arista b/426375784 + // FNT only issue, non-breakout ports have breakout config + FrBreakoutFix bool `protobuf:"varint,293,opt,name=fr_breakout_fix,json=frBreakoutFix,proto3" json:"fr_breakout_fix,omitempty"` + // Cisco b/421356455 + // numPhysicalChannels is not supported + NumPhysicalChannelsUnsupported bool `protobuf:"varint,294,opt,name=num_physical_channels_unsupported,json=numPhysicalChannelsUnsupported,proto3" json:"num_physical_channels_unsupported,omitempty"` + // UnsupportedQoSOutputServicePolicy for devices that do not support qos output service-policy + UnsupportedQosOutputServicePolicy bool `protobuf:"varint,295,opt,name=unsupported_qos_output_service_policy,json=unsupportedQosOutputServicePolicy,proto3" json:"unsupported_qos_output_service_policy,omitempty"` + // InterfaceOutputQueueNonStandardName for devices with non-standard output queue names + InterfaceOutputQueueNonStandardName bool `protobuf:"varint,296,opt,name=interface_output_queue_non_standard_name,json=interfaceOutputQueueNonStandardName,proto3" json:"interface_output_queue_non_standard_name,omitempty"` + // MplsExpIngressClassifierUnsupported for devices that do not support ingress mpls exp field classification + MplsExpIngressClassifierOcUnsupported bool `protobuf:"varint,297,opt,name=mpls_exp_ingress_classifier_oc_unsupported,json=mplsExpIngressClassifierOcUnsupported,proto3" json:"mpls_exp_ingress_classifier_oc_unsupported,omitempty"` + // Devices that do not propagate IGP metric through redistribution + DefaultNoIgpMetricPropagation bool `protobuf:"varint,298,opt,name=default_no_igp_metric_propagation,json=defaultNoIgpMetricPropagation,proto3" json:"default_no_igp_metric_propagation,omitempty"` + // Skip setting send-community-type in bgp peer-group config + SkipBgpPeerGroupSendCommunityType bool `protobuf:"varint,299,opt,name=skip_bgp_peer_group_send_community_type,json=skipBgpPeerGroupSendCommunityType,proto3" json:"skip_bgp_peer_group_send_community_type,omitempty"` + // Devices that does have different AS path prepend order. + // juniper : b/425632068 + BgpAsPathPrependOrderMismtach bool `protobuf:"varint,300,opt,name=bgp_as_path_prepend_order_mismtach,json=bgpAsPathPrependOrderMismtach,proto3" json:"bgp_as_path_prepend_order_mismtach,omitempty"` + // Devices that need explicit swap_src_dst_mac set with loopback_mode + // Nokia b/430183279 + ExplicitSwapSrcDstMacNeededForLoopbackMode bool `protobuf:"varint,301,opt,name=explicit_swap_src_dst_mac_needed_for_loopback_mode,json=explicitSwapSrcDstMacNeededForLoopbackMode,proto3" json:"explicit_swap_src_dst_mac_needed_for_loopback_mode,omitempty"` + // link_local_instead_of_nh is set to true for devices that give + // link-local address instead of NH in AFT. + LinkLocalInsteadOfNh bool `protobuf:"varint,302,opt,name=link_local_instead_of_nh,json=linkLocalInsteadOfNh,proto3" json:"link_local_instead_of_nh,omitempty"` + // low_scale_aft returns true if device requires low scale AFT. + LowScaleAft bool `protobuf:"varint,303,opt,name=low_scale_aft,json=lowScaleAft,proto3" json:"low_scale_aft,omitempty"` + // Devices that do not support system-description config path + // Nokia b/431929861 + MissingSystemDescriptionConfigPath bool `protobuf:"varint,304,opt,name=missing_system_description_config_path,json=missingSystemDescriptionConfigPath,proto3" json:"missing_system_description_config_path,omitempty"` + // Juniper b/428613305 + // FEC uncorrectable errors accumulate over time and are not cleared unless the component is reset on target + NonIntervalFecErrorCounter bool `protobuf:"varint,305,opt,name=non_interval_fec_error_counter,json=nonIntervalFecErrorCounter,proto3" json:"non_interval_fec_error_counter,omitempty"` + // Device does not support ntp source address + NtpSourceAddressUnsupported bool `protobuf:"varint,306,opt,name=ntp_source_address_unsupported,json=ntpSourceAddressUnsupported,proto3" json:"ntp_source_address_unsupported,omitempty"` + // Devices does not support static mpls lsp + StaticMplsLspOcUnsupported bool `protobuf:"varint,307,opt,name=static_mpls_lsp_oc_unsupported,json=staticMplsLspOcUnsupported,proto3" json:"static_mpls_lsp_oc_unsupported,omitempty"` + // Device doesnot support gre enacapsulation + GreDecapsulationOcUnsupported bool `protobuf:"varint,308,opt,name=gre_decapsulation_oc_unsupported,json=greDecapsulationOcUnsupported,proto3" json:"gre_decapsulation_oc_unsupported,omitempty"` + // SRGB and SLGB config through OC is not reflecting + IsisSrgbSrlbUnsupported bool `protobuf:"varint,309,opt,name=isis_srgb_srlb_unsupported,json=isisSrgbSrlbUnsupported,proto3" json:"isis_srgb_srlb_unsupported,omitempty"` + // Prefix segment configuration not supported + IsisSrPrefixSegmentConfigUnsupported bool `protobuf:"varint,310,opt,name=isis_sr_prefix_segment_config_unsupported,json=isisSrPrefixSegmentConfigUnsupported,proto3" json:"isis_sr_prefix_segment_config_unsupported,omitempty"` + // node segment configuration not supported + IsisSrNodeSegmentConfigUnsupported bool `protobuf:"varint,311,opt,name=isis_sr_node_segment_config_unsupported,json=isisSrNodeSegmentConfigUnsupported,proto3" json:"isis_sr_node_segment_config_unsupported,omitempty"` + // Devices that do not support policy forwarding on next-hop + PolicyForwardingToNextHopOcUnsupported bool `protobuf:"varint,312,opt,name=policy_forwarding_to_next_hop_oc_unsupported,json=policyForwardingToNextHopOcUnsupported,proto3" json:"policy_forwarding_to_next_hop_oc_unsupported,omitempty"` + // Cisco: b/402672689 + // Devices that support sflow ingress min sampling rate of 1/N with N<1,000,000 can use + // this deviation to set specific value of N. Returns N=1,000,000 by default. + SflowIngressMinSamplingRate uint32 `protobuf:"varint,313,opt,name=sflow_ingress_min_sampling_rate,json=sflowIngressMinSamplingRate,proto3" json:"sflow_ingress_min_sampling_rate,omitempty"` + // DUT not supporting with qos remarking + // Arista: b/415889077 + QosRemarkOcUnsupported bool `protobuf:"varint,314,opt,name=qos_remark_oc_unsupported,json=qosRemarkOcUnsupported,proto3" json:"qos_remark_oc_unsupported,omitempty"` + // Devices that do not support policy forwarding encapsulate gre action + // Arista: b/409347274 + PolicyForwardingGreEncapsulationOcUnsupported bool `protobuf:"varint,315,opt,name=policy_forwarding_gre_encapsulation_oc_unsupported,json=policyForwardingGreEncapsulationOcUnsupported,proto3" json:"policy_forwarding_gre_encapsulation_oc_unsupported,omitempty"` + // policy rule based counters unsupported + // Arista : https://partnerissuetracker.corp.google.com/issues/425628787 + PolicyRuleCountersOcUnsupported bool `protobuf:"varint,316,opt,name=policy_rule_counters_oc_unsupported,json=policyRuleCountersOcUnsupported,proto3" json:"policy_rule_counters_oc_unsupported,omitempty"` + // Devices that must have OTN to ETH assignment. + // Arista : https://partnerissuetracker.corp.google.com/issues/434922681 + OtnToEthAssignment bool `protobuf:"varint,317,opt,name=otn_to_eth_assignment,json=otnToEthAssignment,proto3" json:"otn_to_eth_assignment,omitempty"` + // Devices that do not support import export policies configured in network instance + NetworkInstanceImportExportPolicyOcUnsuppored bool `protobuf:"varint,318,opt,name=network_instance_import_export_policy_oc_unsuppored,json=networkInstanceImportExportPolicyOcUnsuppored,proto3" json:"network_instance_import_export_policy_oc_unsuppored,omitempty"` + // Device does not support 'origin' field in gNMI/gNOI RPC paths. + // Arista: https://partnerissuetracker.corp.google.com/issues/439656904 + SkipOrigin bool `protobuf:"varint,319,opt,name=skip_origin,json=skipOrigin,proto3" json:"skip_origin,omitempty"` + // Devices that support pre-defined max ecmp paths + // Juniper b/422688435 + PredefinedMaxEcmpPaths bool `protobuf:"varint,320,opt,name=predefined_max_ecmp_paths,json=predefinedMaxEcmpPaths,proto3" json:"predefined_max_ecmp_paths,omitempty"` + // b/425503156 + // Device does not support decapsulation group + DecapsulateGueOcUnsupported bool `protobuf:"varint,321,opt,name=decapsulate_gue_oc_unsupported,json=decapsulateGueOcUnsupported,proto3" json:"decapsulate_gue_oc_unsupported,omitempty"` + // Device does not support line-port configuration on optical channel + // components for Nokia and Arista. + LinePortUnsupported bool `protobuf:"varint,322,opt,name=line_port_unsupported,json=linePortUnsupported,proto3" json:"line_port_unsupported,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache } func (x *Metadata_Deviations) Reset() { *x = Metadata_Deviations{} - if protoimpl.UnsafeEnabled { - mi := &file_metadata_proto_msgTypes[2] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_metadata_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Metadata_Deviations) String() string { @@ -1044,7 +1160,7 @@ func (*Metadata_Deviations) ProtoMessage() {} func (x *Metadata_Deviations) ProtoReflect() protoreflect.Message { mi := &file_metadata_proto_msgTypes[2] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2473,13 +2589,6 @@ func (x *Metadata_Deviations) GetEnableMultipathUnderAfiSafi() bool { return false } -func (x *Metadata_Deviations) GetBgpAllowownasDiffDefaultValue() bool { - if x != nil { - return x.BgpAllowownasDiffDefaultValue - } - return false -} - func (x *Metadata_Deviations) GetOtnChannelAssignmentCiscoNumbering() bool { if x != nil { return x.OtnChannelAssignmentCiscoNumbering @@ -2753,22 +2862,369 @@ func (x *Metadata_Deviations) GetBgpSetMedActionUnsupported() bool { return false } +func (x *Metadata_Deviations) GetNextHopGroupConfigUnsupported() bool { + if x != nil { + return x.NextHopGroupConfigUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetQosShaperConfigUnsupported() bool { + if x != nil { + return x.QosShaperConfigUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetQosShaperStateUnsupported() bool { + if x != nil { + return x.QosShaperStateUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetEthernetOverMplsogreUnsupported() bool { + if x != nil { + return x.EthernetOverMplsogreUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSflowUnsupported() bool { + if x != nil { + return x.SflowUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetMplsUnsupported() bool { + if x != nil { + return x.MplsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetMacsecUnsupported() bool { + if x != nil { + return x.MacsecUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetGueGreDecapUnsupported() bool { + if x != nil { + return x.GueGreDecapUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetMplsLabelClassificationUnsupported() bool { + if x != nil { + return x.MplsLabelClassificationUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetLocalProxyUnsupported() bool { + if x != nil { + return x.LocalProxyUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetStaticMplsUnsupported() bool { + if x != nil { + return x.StaticMplsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetQosClassificationUnsupported() bool { + if x != nil { + return x.QosClassificationUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPolicyForwardingUnsupported() bool { + if x != nil { + return x.PolicyForwardingUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetCfmUnsupported() bool { + if x != nil { + return x.CfmUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetLabelRangeUnsupported() bool { + if x != nil { + return x.LabelRangeUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetStaticArpUnsupported() bool { + if x != nil { + return x.StaticArpUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetInterfacePolicyForwardingUnsupported() bool { + if x != nil { + return x.InterfacePolicyForwardingUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetUseOldOcPathStaticLspNh() bool { + if x != nil { + return x.UseOldOcPathStaticLspNh + } + return false +} + +func (x *Metadata_Deviations) GetConfigLeafCreateRequired() bool { + if x != nil { + return x.ConfigLeafCreateRequired + } + return false +} + +func (x *Metadata_Deviations) GetSkipInterfaceNameCheck() bool { + if x != nil { + return x.SkipInterfaceNameCheck + } + return false +} + +func (x *Metadata_Deviations) GetFrBreakoutFix() bool { + if x != nil { + return x.FrBreakoutFix + } + return false +} + +func (x *Metadata_Deviations) GetNumPhysicalChannelsUnsupported() bool { + if x != nil { + return x.NumPhysicalChannelsUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetUnsupportedQosOutputServicePolicy() bool { + if x != nil { + return x.UnsupportedQosOutputServicePolicy + } + return false +} + +func (x *Metadata_Deviations) GetInterfaceOutputQueueNonStandardName() bool { + if x != nil { + return x.InterfaceOutputQueueNonStandardName + } + return false +} + +func (x *Metadata_Deviations) GetMplsExpIngressClassifierOcUnsupported() bool { + if x != nil { + return x.MplsExpIngressClassifierOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetDefaultNoIgpMetricPropagation() bool { + if x != nil { + return x.DefaultNoIgpMetricPropagation + } + return false +} + +func (x *Metadata_Deviations) GetSkipBgpPeerGroupSendCommunityType() bool { + if x != nil { + return x.SkipBgpPeerGroupSendCommunityType + } + return false +} + +func (x *Metadata_Deviations) GetBgpAsPathPrependOrderMismtach() bool { + if x != nil { + return x.BgpAsPathPrependOrderMismtach + } + return false +} + +func (x *Metadata_Deviations) GetExplicitSwapSrcDstMacNeededForLoopbackMode() bool { + if x != nil { + return x.ExplicitSwapSrcDstMacNeededForLoopbackMode + } + return false +} + +func (x *Metadata_Deviations) GetLinkLocalInsteadOfNh() bool { + if x != nil { + return x.LinkLocalInsteadOfNh + } + return false +} + +func (x *Metadata_Deviations) GetLowScaleAft() bool { + if x != nil { + return x.LowScaleAft + } + return false +} + +func (x *Metadata_Deviations) GetMissingSystemDescriptionConfigPath() bool { + if x != nil { + return x.MissingSystemDescriptionConfigPath + } + return false +} + +func (x *Metadata_Deviations) GetNonIntervalFecErrorCounter() bool { + if x != nil { + return x.NonIntervalFecErrorCounter + } + return false +} + +func (x *Metadata_Deviations) GetNtpSourceAddressUnsupported() bool { + if x != nil { + return x.NtpSourceAddressUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetStaticMplsLspOcUnsupported() bool { + if x != nil { + return x.StaticMplsLspOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetGreDecapsulationOcUnsupported() bool { + if x != nil { + return x.GreDecapsulationOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIsisSrgbSrlbUnsupported() bool { + if x != nil { + return x.IsisSrgbSrlbUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIsisSrPrefixSegmentConfigUnsupported() bool { + if x != nil { + return x.IsisSrPrefixSegmentConfigUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetIsisSrNodeSegmentConfigUnsupported() bool { + if x != nil { + return x.IsisSrNodeSegmentConfigUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPolicyForwardingToNextHopOcUnsupported() bool { + if x != nil { + return x.PolicyForwardingToNextHopOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetSflowIngressMinSamplingRate() uint32 { + if x != nil { + return x.SflowIngressMinSamplingRate + } + return 0 +} + +func (x *Metadata_Deviations) GetQosRemarkOcUnsupported() bool { + if x != nil { + return x.QosRemarkOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPolicyForwardingGreEncapsulationOcUnsupported() bool { + if x != nil { + return x.PolicyForwardingGreEncapsulationOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetPolicyRuleCountersOcUnsupported() bool { + if x != nil { + return x.PolicyRuleCountersOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetOtnToEthAssignment() bool { + if x != nil { + return x.OtnToEthAssignment + } + return false +} + +func (x *Metadata_Deviations) GetNetworkInstanceImportExportPolicyOcUnsuppored() bool { + if x != nil { + return x.NetworkInstanceImportExportPolicyOcUnsuppored + } + return false +} + +func (x *Metadata_Deviations) GetSkipOrigin() bool { + if x != nil { + return x.SkipOrigin + } + return false +} + +func (x *Metadata_Deviations) GetPredefinedMaxEcmpPaths() bool { + if x != nil { + return x.PredefinedMaxEcmpPaths + } + return false +} + +func (x *Metadata_Deviations) GetDecapsulateGueOcUnsupported() bool { + if x != nil { + return x.DecapsulateGueOcUnsupported + } + return false +} + +func (x *Metadata_Deviations) GetLinePortUnsupported() bool { + if x != nil { + return x.LinePortUnsupported + } + return false +} + type Metadata_PlatformExceptions struct { - state protoimpl.MessageState - sizeCache protoimpl.SizeCache + state protoimpl.MessageState `protogen:"open.v1"` + Platform *Metadata_Platform `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` + Deviations *Metadata_Deviations `protobuf:"bytes,2,opt,name=deviations,proto3" json:"deviations,omitempty"` unknownFields protoimpl.UnknownFields - - Platform *Metadata_Platform `protobuf:"bytes,1,opt,name=platform,proto3" json:"platform,omitempty"` - Deviations *Metadata_Deviations `protobuf:"bytes,2,opt,name=deviations,proto3" json:"deviations,omitempty"` + sizeCache protoimpl.SizeCache } func (x *Metadata_PlatformExceptions) Reset() { *x = Metadata_PlatformExceptions{} - if protoimpl.UnsafeEnabled { - mi := &file_metadata_proto_msgTypes[3] - ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) - ms.StoreMessageInfo(mi) - } + mi := &file_metadata_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) } func (x *Metadata_PlatformExceptions) String() string { @@ -2779,7 +3235,7 @@ func (*Metadata_PlatformExceptions) ProtoMessage() {} func (x *Metadata_PlatformExceptions) ProtoReflect() protoreflect.Message { mi := &file_metadata_proto_msgTypes[3] - if protoimpl.UnsafeEnabled && x != nil { + if x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { ms.StoreMessageInfo(mi) @@ -2810,1228 +3266,362 @@ func (x *Metadata_PlatformExceptions) GetDeviations() *Metadata_Deviations { var File_metadata_proto protoreflect.FileDescriptor -var file_metadata_proto_rawDesc = []byte{ - 0x0a, 0x0e, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x12, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, - 0x74, 0x69, 0x6e, 0x67, 0x1a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, - 0x2f, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2f, 0x6f, 0x6e, 0x64, 0x61, - 0x74, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x74, 0x65, 0x73, 0x74, 0x62, 0x65, - 0x64, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xd6, 0x95, 0x01, 0x0a, 0x08, 0x4d, 0x65, 0x74, - 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x12, 0x0a, 0x04, 0x75, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x04, 0x75, 0x75, 0x69, 0x64, 0x12, 0x17, 0x0a, 0x07, 0x70, 0x6c, 0x61, - 0x6e, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x70, 0x6c, 0x61, 0x6e, - 0x49, 0x64, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x07, 0x74, 0x65, 0x73, 0x74, 0x62, 0x65, 0x64, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x24, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, - 0x61, 0x74, 0x61, 0x2e, 0x54, 0x65, 0x73, 0x74, 0x62, 0x65, 0x64, 0x52, 0x07, 0x74, 0x65, 0x73, - 0x74, 0x62, 0x65, 0x64, 0x12, 0x60, 0x0a, 0x13, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, - 0x5f, 0x65, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x2f, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, - 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, - 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x12, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x45, 0x78, 0x63, 0x65, - 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x35, 0x0a, 0x04, 0x74, 0x61, 0x67, 0x73, 0x18, 0x06, - 0x20, 0x03, 0x28, 0x0e, 0x32, 0x21, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, - 0x74, 0x61, 0x2e, 0x54, 0x61, 0x67, 0x73, 0x52, 0x04, 0x74, 0x61, 0x67, 0x73, 0x12, 0x2c, 0x0a, - 0x12, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x70, 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x74, - 0x65, 0x73, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x70, 0x61, 0x74, 0x68, 0x50, - 0x72, 0x65, 0x73, 0x65, 0x6e, 0x63, 0x65, 0x54, 0x65, 0x73, 0x74, 0x1a, 0xb8, 0x01, 0x0a, 0x08, - 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x2e, 0x0a, 0x06, 0x76, 0x65, 0x6e, 0x64, - 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x16, 0x2e, 0x6f, 0x6e, 0x64, 0x61, 0x74, - 0x72, 0x61, 0x2e, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65, 0x2e, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, - 0x52, 0x06, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x12, 0x30, 0x0a, 0x14, 0x68, 0x61, 0x72, 0x64, - 0x77, 0x61, 0x72, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x12, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, - 0x4d, 0x6f, 0x64, 0x65, 0x6c, 0x52, 0x65, 0x67, 0x65, 0x78, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x6f, - 0x66, 0x74, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x72, - 0x65, 0x67, 0x65, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x14, 0x73, 0x6f, 0x66, 0x74, - 0x77, 0x61, 0x72, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x67, 0x65, 0x78, - 0x4a, 0x04, 0x08, 0x02, 0x10, 0x03, 0x52, 0x0e, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, - 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x1a, 0xaa, 0x8c, 0x01, 0x0a, 0x0a, 0x44, 0x65, 0x76, 0x69, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x69, 0x70, 0x76, 0x34, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, - 0x67, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x18, 0x74, 0x72, 0x61, 0x63, - 0x65, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x74, 0x72, 0x61, 0x63, - 0x65, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x1a, 0x74, 0x72, 0x61, 0x63, 0x65, 0x72, 0x6f, 0x75, 0x74, - 0x65, 0x5f, 0x6c, 0x34, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x75, 0x64, - 0x70, 0x18, 0x03, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x74, 0x72, 0x61, 0x63, 0x65, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x4c, 0x34, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x55, 0x64, 0x70, - 0x12, 0x3a, 0x0a, 0x19, 0x70, 0x72, 0x65, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x72, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x17, 0x70, 0x72, 0x65, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x52, 0x65, - 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x73, 0x12, 0x57, 0x0a, 0x28, - 0x68, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x77, 0x65, 0x69, - 0x67, 0x68, 0x74, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x74, - 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x01, 0x52, 0x25, - 0x68, 0x69, 0x65, 0x72, 0x61, 0x72, 0x63, 0x68, 0x69, 0x63, 0x61, 0x6c, 0x57, 0x65, 0x69, 0x67, - 0x68, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6c, 0x65, - 0x72, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x45, 0x0a, 0x1f, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x5f, 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, - 0x69, 0x73, 0x69, 0x73, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, - 0x79, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x52, 0x0a, 0x26, - 0x69, 0x73, 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x6c, - 0x65, 0x76, 0x65, 0x6c, 0x31, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x69, 0x73, - 0x69, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4c, 0x65, 0x76, 0x65, 0x6c, - 0x31, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, - 0x12, 0x41, 0x0a, 0x1d, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x73, 0x69, 0x6e, 0x67, 0x6c, 0x65, 0x5f, - 0x74, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x69, 0x73, 0x69, 0x73, 0x53, 0x69, 0x6e, - 0x67, 0x6c, 0x65, 0x54, 0x6f, 0x70, 0x6f, 0x6c, 0x6f, 0x67, 0x79, 0x52, 0x65, 0x71, 0x75, 0x69, - 0x72, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x1e, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x69, 0x73, 0x69, - 0x73, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x51, 0x0a, 0x26, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, - 0x63, 0x65, 0x5f, 0x61, 0x66, 0x69, 0x5f, 0x73, 0x61, 0x66, 0x69, 0x5f, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, - 0x67, 0x49, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x41, 0x66, - 0x69, 0x53, 0x61, 0x66, 0x69, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x54, 0x0a, 0x27, 0x69, - 0x73, 0x69, 0x73, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, - 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x69, 0x73, - 0x69, 0x73, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x4e, 0x6f, 0x74, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x12, 0x58, 0x0a, 0x29, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, - 0x69, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x0d, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x69, 0x73, 0x69, 0x73, 0x45, 0x78, 0x70, 0x6c, 0x69, 0x63, - 0x69, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x49, 0x0a, 0x21, 0x69, - 0x73, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x74, 0x61, 0x72, 0x74, 0x5f, 0x73, 0x75, 0x70, 0x70, - 0x72, 0x65, 0x73, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0x0e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x69, 0x73, 0x69, 0x73, 0x52, 0x65, 0x73, 0x74, - 0x61, 0x72, 0x74, 0x53, 0x75, 0x70, 0x70, 0x72, 0x65, 0x73, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x69, 0x70, 0x5f, 0x6e, 0x65, 0x69, - 0x67, 0x68, 0x62, 0x6f, 0x72, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x18, 0x0f, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x11, 0x69, 0x70, 0x4e, 0x65, 0x69, 0x67, 0x68, 0x62, 0x6f, 0x72, 0x4d, - 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x2f, 0x0a, 0x13, 0x6f, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x18, 0x10, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x12, 0x6f, 0x73, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x4e, - 0x6f, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x12, 0x37, 0x0a, 0x18, 0x6f, 0x73, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x6e, 0x64, 0x62, 0x79, - 0x5f, 0x72, 0x70, 0x18, 0x11, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x6f, 0x73, 0x69, 0x6e, 0x73, - 0x74, 0x61, 0x6c, 0x6c, 0x46, 0x6f, 0x72, 0x53, 0x74, 0x61, 0x6e, 0x64, 0x62, 0x79, 0x52, 0x70, - 0x12, 0x50, 0x0a, 0x25, 0x6c, 0x6c, 0x64, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, - 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, - 0x64, 0x65, 0x5f, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x18, 0x12, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x21, 0x6c, 0x6c, 0x64, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x47, 0x6c, 0x6f, 0x62, - 0x61, 0x6c, 0x12, 0x55, 0x0a, 0x28, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x67, - 0x70, 0x5f, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x15, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, 0x67, 0x70, - 0x4c, 0x61, 0x73, 0x74, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x47, 0x0a, 0x20, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x16, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x65, - 0x66, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x17, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x14, 0x73, 0x74, 0x61, 0x74, 0x65, 0x50, 0x61, 0x74, 0x68, 0x55, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x3f, 0x0a, 0x1d, 0x69, 0x70, 0x76, 0x36, - 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x67, 0x72, 0x69, 0x62, - 0x69, 0x5f, 0x6e, 0x68, 0x5f, 0x64, 0x6d, 0x61, 0x63, 0x18, 0x18, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x18, 0x69, 0x70, 0x76, 0x36, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x46, 0x6f, 0x72, 0x47, 0x72, - 0x69, 0x62, 0x69, 0x4e, 0x68, 0x44, 0x6d, 0x61, 0x63, 0x12, 0x45, 0x0a, 0x1f, 0x65, 0x63, 0x6e, - 0x5f, 0x70, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x64, 0x5f, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x19, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x1c, 0x65, 0x63, 0x6e, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, 0x52, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x44, 0x65, 0x66, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x12, 0x45, 0x0a, 0x1f, 0x69, 0x70, 0x76, 0x36, 0x5f, 0x64, 0x69, 0x73, 0x63, 0x61, 0x72, 0x64, - 0x65, 0x64, 0x5f, 0x70, 0x6b, 0x74, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x69, 0x70, 0x76, 0x36, 0x44, - 0x69, 0x73, 0x63, 0x61, 0x72, 0x64, 0x65, 0x64, 0x50, 0x6b, 0x74, 0x73, 0x55, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x1e, 0x64, 0x72, 0x6f, 0x70, 0x5f, - 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6c, 0x65, 0x61, 0x76, 0x65, 0x73, 0x5f, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x1b, 0x64, 0x72, 0x6f, 0x70, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x65, 0x61, 0x76, 0x65, - 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x1c, - 0x63, 0x6c, 0x69, 0x5f, 0x74, 0x61, 0x6b, 0x65, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x63, 0x65, 0x64, - 0x65, 0x6e, 0x63, 0x65, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x6f, 0x63, 0x18, 0x1d, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x18, 0x63, 0x6c, 0x69, 0x54, 0x61, 0x6b, 0x65, 0x73, 0x50, 0x72, 0x65, 0x63, - 0x65, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x4f, 0x76, 0x65, 0x72, 0x4f, 0x63, 0x12, 0x3f, 0x0a, 0x1c, - 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x5f, - 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x1e, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x19, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x49, 0x6e, 0x70, - 0x75, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x3b, 0x0a, - 0x1a, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x5f, 0x63, 0x68, 0x69, 0x70, 0x5f, 0x69, 0x64, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x1f, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x17, 0x73, 0x77, 0x69, 0x74, 0x63, 0x68, 0x43, 0x68, 0x69, 0x70, 0x49, 0x64, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x51, 0x0a, 0x25, 0x62, 0x61, - 0x63, 0x6b, 0x70, 0x6c, 0x61, 0x6e, 0x65, 0x5f, 0x66, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x5f, 0x63, - 0x61, 0x70, 0x61, 0x63, 0x69, 0x74, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0x20, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x62, 0x61, 0x63, 0x6b, 0x70, - 0x6c, 0x61, 0x6e, 0x65, 0x46, 0x61, 0x63, 0x69, 0x6e, 0x67, 0x43, 0x61, 0x70, 0x61, 0x63, 0x69, - 0x74, 0x79, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x49, 0x0a, - 0x21, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x65, 0x72, 0x73, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x18, 0x21, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x46, 0x72, 0x6f, 0x6d, 0x43, - 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x5a, 0x0a, 0x2b, 0x6e, 0x6f, 0x5f, 0x6d, - 0x69, 0x78, 0x5f, 0x6f, 0x66, 0x5f, 0x74, 0x61, 0x67, 0x67, 0x65, 0x64, 0x5f, 0x61, 0x6e, 0x64, - 0x5f, 0x75, 0x6e, 0x74, 0x61, 0x67, 0x67, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x73, 0x18, 0x22, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x6e, - 0x6f, 0x4d, 0x69, 0x78, 0x4f, 0x66, 0x54, 0x61, 0x67, 0x67, 0x65, 0x64, 0x41, 0x6e, 0x64, 0x55, - 0x6e, 0x74, 0x61, 0x67, 0x67, 0x65, 0x64, 0x53, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x73, 0x12, 0x34, 0x0a, 0x16, 0x73, 0x77, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x25, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x77, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x42, 0x0a, 0x1d, 0x73, 0x74, - 0x6f, 0x72, 0x61, 0x67, 0x65, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x27, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x1b, 0x73, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2e, - 0x0a, 0x13, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, - 0x73, 0x70, 0x65, 0x65, 0x64, 0x18, 0x29, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x65, 0x78, 0x70, - 0x6c, 0x69, 0x63, 0x69, 0x74, 0x50, 0x6f, 0x72, 0x74, 0x53, 0x70, 0x65, 0x65, 0x64, 0x12, 0x48, - 0x0a, 0x21, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, - 0x76, 0x72, 0x66, 0x18, 0x2a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x65, 0x78, 0x70, 0x6c, 0x69, - 0x63, 0x69, 0x74, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x49, 0x6e, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x72, 0x66, 0x12, 0x4f, 0x0a, 0x24, 0x73, 0x75, 0x62, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, - 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, - 0x18, 0x2c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x73, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, - 0x72, 0x73, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x12, 0x23, 0x0a, 0x0d, 0x63, 0x6f, 0x6e, - 0x6e, 0x65, 0x63, 0x74, 0x5f, 0x72, 0x65, 0x74, 0x72, 0x79, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x0c, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x52, 0x65, 0x74, 0x72, 0x79, 0x12, 0x49, - 0x0a, 0x22, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x6d, 0x61, 0x63, 0x5f, 0x6f, 0x76, 0x65, 0x72, - 0x72, 0x69, 0x64, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, - 0x5f, 0x61, 0x72, 0x70, 0x18, 0x2e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x67, 0x72, 0x69, 0x62, - 0x69, 0x4d, 0x61, 0x63, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x57, 0x69, 0x74, 0x68, - 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x72, 0x70, 0x12, 0x4a, 0x0a, 0x22, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, - 0x61, 0x66, 0x69, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, - 0x2f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x41, 0x66, 0x69, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x56, 0x0a, 0x28, 0x67, 0x6e, 0x6f, 0x69, 0x5f, 0x66, 0x61, - 0x62, 0x72, 0x69, 0x63, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x72, - 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x30, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x67, 0x6e, 0x6f, 0x69, 0x46, 0x61, 0x62, - 0x72, 0x69, 0x63, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x62, 0x6f, - 0x6f, 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x44, 0x0a, - 0x1f, 0x6e, 0x74, 0x70, 0x5f, 0x6e, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x5f, 0x76, 0x72, 0x66, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0x31, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x6e, 0x74, 0x70, 0x4e, 0x6f, 0x6e, 0x44, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x72, 0x66, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x12, 0x1e, 0x0a, 0x0b, 0x6f, 0x6d, 0x69, 0x74, 0x5f, 0x6c, 0x32, 0x5f, 0x6d, - 0x74, 0x75, 0x18, 0x32, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x6f, 0x6d, 0x69, 0x74, 0x4c, 0x32, - 0x4d, 0x74, 0x75, 0x12, 0x46, 0x0a, 0x20, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x70, 0x6f, 0x77, 0x65, - 0x72, 0x5f, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x18, 0x33, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x73, - 0x6b, 0x69, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x61, 0x72, - 0x64, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x62, - 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, 0x18, - 0x3c, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x62, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x44, 0x65, 0x6c, - 0x69, 0x6d, 0x69, 0x74, 0x65, 0x72, 0x12, 0x2e, 0x0a, 0x13, 0x62, 0x67, 0x70, 0x5f, 0x74, 0x6f, - 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x3d, 0x20, - 0x01, 0x28, 0x05, 0x52, 0x11, 0x62, 0x67, 0x70, 0x54, 0x6f, 0x6c, 0x65, 0x72, 0x61, 0x6e, 0x63, - 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x4d, 0x0a, 0x24, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x71, - 0x75, 0x61, 0x6c, 0x5f, 0x77, 0x61, 0x69, 0x74, 0x5f, 0x61, 0x66, 0x74, 0x65, 0x72, 0x5f, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x3e, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x6c, 0x69, 0x6e, 0x6b, 0x51, 0x75, 0x61, 0x6c, 0x57, 0x61, - 0x69, 0x74, 0x41, 0x66, 0x74, 0x65, 0x72, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x1e, 0x67, 0x6e, 0x6f, 0x69, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x5f, 0x65, 0x6d, 0x70, 0x74, 0x79, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x6f, - 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x3f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x67, - 0x6e, 0x6f, 0x69, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x53, 0x75, - 0x62, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x56, 0x0a, 0x28, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x74, - 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x40, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x6e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x54, 0x61, 0x62, - 0x6c, 0x65, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x64, 0x12, 0x33, 0x0a, 0x16, 0x62, 0x67, 0x70, 0x5f, 0x6d, 0x64, 0x35, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x72, 0x65, 0x73, 0x65, 0x74, 0x18, 0x41, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x13, 0x62, 0x67, 0x70, 0x4d, 0x64, 0x35, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x65, 0x74, 0x12, 0x4b, 0x0a, 0x23, 0x64, 0x65, 0x71, 0x75, 0x65, - 0x75, 0x65, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x63, 0x6f, - 0x75, 0x6e, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x73, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x73, 0x18, 0x42, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x64, 0x65, 0x71, 0x75, 0x65, 0x75, 0x65, 0x44, 0x65, 0x6c, - 0x65, 0x74, 0x65, 0x4e, 0x6f, 0x74, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x64, 0x41, 0x73, 0x44, - 0x72, 0x6f, 0x70, 0x73, 0x12, 0x2a, 0x0a, 0x11, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x72, 0x69, - 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x6f, 0x6e, 0x6c, 0x79, 0x18, 0x43, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x0f, 0x67, 0x72, 0x69, 0x62, 0x69, 0x52, 0x69, 0x62, 0x61, 0x63, 0x6b, 0x4f, 0x6e, 0x6c, 0x79, - 0x12, 0x36, 0x0a, 0x17, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x5f, 0x61, 0x74, - 0x6f, 0x6d, 0x69, 0x63, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x44, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x15, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, 0x41, 0x74, 0x6f, 0x6d, - 0x69, 0x63, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x3b, 0x0a, 0x1a, 0x6d, 0x69, 0x73, 0x73, - 0x69, 0x6e, 0x67, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x45, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6e, 0x67, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x46, 0x6f, 0x72, 0x44, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x46, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x12, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x74, 0x6f, - 0x63, 0x6f, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x34, 0x0a, 0x16, 0x67, 0x6e, 0x6f, 0x69, 0x5f, - 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x74, - 0x68, 0x18, 0x47, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x67, 0x6e, 0x6f, 0x69, 0x53, 0x75, 0x62, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x50, 0x61, 0x74, 0x68, 0x12, 0x4c, 0x0a, - 0x23, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x5f, 0x76, 0x72, 0x66, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x48, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x69, 0x6e, 0x74, 0x65, - 0x72, 0x66, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x72, 0x66, 0x42, 0x65, - 0x66, 0x6f, 0x72, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2c, 0x0a, 0x12, 0x64, - 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x76, 0x6c, 0x61, 0x6e, 0x5f, 0x69, - 0x64, 0x18, 0x49, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x64, 0x65, 0x70, 0x72, 0x65, 0x63, 0x61, - 0x74, 0x65, 0x64, 0x56, 0x6c, 0x61, 0x6e, 0x49, 0x64, 0x12, 0x58, 0x0a, 0x2a, 0x67, 0x72, 0x69, - 0x62, 0x69, 0x5f, 0x6d, 0x61, 0x63, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, - 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x61, 0x72, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x18, 0x4a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x67, - 0x72, 0x69, 0x62, 0x69, 0x4d, 0x61, 0x63, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x53, - 0x74, 0x61, 0x74, 0x69, 0x63, 0x41, 0x72, 0x70, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x12, 0x2b, 0x0a, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, - 0x5f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x18, 0x4b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x71, 0x6f, 0x73, 0x5f, 0x6f, 0x63, 0x74, 0x65, 0x74, 0x73, 0x18, 0x4c, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x71, 0x6f, 0x73, 0x4f, 0x63, 0x74, 0x65, 0x74, 0x73, 0x12, - 0x30, 0x0a, 0x14, 0x63, 0x70, 0x75, 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x61, - 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, 0x72, 0x18, 0x4d, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x63, - 0x70, 0x75, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, 0x6e, 0x63, 0x65, 0x73, 0x74, 0x6f, - 0x72, 0x12, 0x41, 0x0a, 0x1d, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x64, 0x5f, 0x73, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, - 0x5f, 0x30, 0x18, 0x4e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x64, 0x53, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, - 0x61, 0x63, 0x65, 0x30, 0x12, 0x5f, 0x0a, 0x2d, 0x67, 0x6e, 0x6f, 0x69, 0x5f, 0x73, 0x77, 0x69, - 0x74, 0x63, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x5f, 0x6d, - 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x65, 0x64, 0x18, 0x4f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x28, 0x67, 0x6e, 0x6f, - 0x69, 0x53, 0x77, 0x69, 0x74, 0x63, 0x68, 0x6f, 0x76, 0x65, 0x72, 0x52, 0x65, 0x61, 0x73, 0x6f, - 0x6e, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x55, 0x73, 0x65, 0x72, 0x49, 0x6e, 0x69, 0x74, - 0x69, 0x61, 0x74, 0x65, 0x64, 0x12, 0x38, 0x0a, 0x18, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x5f, 0x6e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x18, 0x50, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, - 0x4f, 0x0a, 0x24, 0x70, 0x34, 0x72, 0x74, 0x5f, 0x75, 0x6e, 0x73, 0x65, 0x74, 0x65, 0x6c, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x69, 0x64, 0x5f, 0x70, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x5f, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, 0x18, 0x51, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x70, - 0x34, 0x72, 0x74, 0x55, 0x6e, 0x73, 0x65, 0x74, 0x65, 0x6c, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x69, 0x64, 0x50, 0x72, 0x69, 0x6d, 0x61, 0x72, 0x79, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x65, 0x64, - 0x12, 0x3b, 0x0a, 0x1a, 0x62, 0x6b, 0x75, 0x70, 0x5f, 0x61, 0x72, 0x62, 0x69, 0x74, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x72, 0x65, 0x73, 0x70, 0x5f, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x52, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x62, 0x6b, 0x75, 0x70, 0x41, 0x72, 0x62, 0x69, 0x74, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x43, 0x6f, 0x64, 0x65, 0x12, 0x49, 0x0a, - 0x22, 0x62, 0x61, 0x63, 0x6b, 0x75, 0x70, 0x5f, 0x6e, 0x68, 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x73, 0x5f, 0x76, 0x72, 0x66, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x64, 0x65, - 0x63, 0x61, 0x70, 0x18, 0x53, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x62, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x4e, 0x68, 0x67, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x56, 0x72, 0x66, 0x57, - 0x69, 0x74, 0x68, 0x44, 0x65, 0x63, 0x61, 0x70, 0x12, 0x43, 0x0a, 0x1e, 0x69, 0x73, 0x69, 0x73, - 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x66, 0x69, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x55, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x1b, 0x69, 0x73, 0x69, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x41, - 0x66, 0x69, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4c, 0x0a, - 0x23, 0x70, 0x34, 0x72, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x5f, 0x74, 0x61, 0x62, - 0x6c, 0x65, 0x5f, 0x65, 0x6e, 0x74, 0x72, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0x56, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x70, 0x34, 0x72, 0x74, - 0x4d, 0x6f, 0x64, 0x69, 0x66, 0x79, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x74, 0x72, 0x79, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x5e, 0x0a, 0x2d, 0x6f, - 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x73, 0x5f, 0x73, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, 0x72, - 0x5f, 0x6f, 0x72, 0x5f, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x18, 0x57, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x27, 0x6f, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x50, - 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x73, 0x53, 0x75, 0x70, 0x65, 0x72, 0x76, 0x69, 0x73, 0x6f, - 0x72, 0x4f, 0x72, 0x4c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x12, 0x42, 0x0a, 0x1e, 0x6f, - 0x73, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x70, 0x61, 0x72, 0x65, - 0x6e, 0x74, 0x5f, 0x69, 0x73, 0x5f, 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x18, 0x58, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1a, 0x6f, 0x73, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x50, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x49, 0x73, 0x43, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x12, - 0x57, 0x0a, 0x2a, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, - 0x73, 0x61, 0x6d, 0x65, 0x5f, 0x6c, 0x31, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x77, - 0x69, 0x74, 0x68, 0x5f, 0x6c, 0x32, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x5b, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x23, 0x69, 0x73, 0x69, 0x73, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x53, 0x61, 0x6d, 0x65, 0x4c, 0x31, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x57, 0x69, 0x74, 0x68, - 0x4c, 0x32, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x57, 0x0a, 0x2a, 0x62, 0x67, 0x70, 0x5f, - 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x64, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, - 0x5f, 0x65, 0x71, 0x75, 0x61, 0x6c, 0x5f, 0x6f, 0x73, 0x70, 0x66, 0x5f, 0x73, 0x65, 0x74, 0x5f, - 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x18, 0x5c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x62, 0x67, - 0x70, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x64, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x45, - 0x71, 0x75, 0x61, 0x6c, 0x4f, 0x73, 0x70, 0x66, 0x53, 0x65, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x12, 0x4e, 0x0a, 0x24, 0x70, 0x34, 0x72, 0x74, 0x5f, 0x67, 0x64, 0x70, 0x5f, 0x72, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x64, 0x6f, 0x74, 0x31, 0x71, 0x5f, 0x73, 0x75, 0x62, - 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x18, 0x5d, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x20, 0x70, 0x34, 0x72, 0x74, 0x47, 0x64, 0x70, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, - 0x44, 0x6f, 0x74, 0x31, 0x71, 0x53, 0x75, 0x62, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x12, 0x59, 0x0a, 0x2a, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x6c, 0x69, - 0x6e, 0x6b, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, - 0x5e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x61, 0x74, 0x65, 0x50, 0x6f, 0x72, 0x74, 0x4c, 0x69, - 0x6e, 0x6b, 0x53, 0x74, 0x61, 0x74, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x26, 0x0a, 0x0f, - 0x73, 0x65, 0x74, 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x75, 0x73, 0x65, 0x72, 0x18, - 0x5f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0d, 0x73, 0x65, 0x74, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, - 0x55, 0x73, 0x65, 0x72, 0x12, 0x4f, 0x0a, 0x24, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, - 0x5f, 0x63, 0x70, 0x75, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x62, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x21, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x43, 0x70, 0x75, 0x55, - 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x53, 0x0a, 0x26, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, - 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x6e, 0x61, - 0x6d, 0x65, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, - 0x63, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x63, 0x6f, 0x6e, 0x73, 0x69, 0x73, 0x74, 0x65, 0x6e, - 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x5c, 0x0a, 0x2b, 0x63, 0x6f, - 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x63, 0x70, - 0x75, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x64, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x27, 0x63, 0x6f, 0x6e, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x65, 0x72, 0x43, 0x61, 0x72, 0x64, 0x43, - 0x70, 0x75, 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x45, 0x0a, 0x1f, 0x66, 0x61, 0x62, 0x72, - 0x69, 0x63, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x65, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x1c, 0x66, 0x61, 0x62, 0x72, 0x69, 0x63, 0x44, 0x72, 0x6f, 0x70, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, - 0x55, 0x0a, 0x27, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6d, 0x65, 0x6d, 0x6f, - 0x72, 0x79, 0x5f, 0x75, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x66, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x24, 0x6c, 0x69, 0x6e, 0x65, 0x63, 0x61, 0x72, 0x64, 0x4d, 0x65, 0x6d, 0x6f, 0x72, 0x79, - 0x55, 0x74, 0x69, 0x6c, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x20, 0x71, 0x6f, 0x73, 0x5f, 0x76, 0x6f, - 0x71, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x67, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x1c, 0x71, 0x6f, 0x73, 0x56, 0x6f, 0x71, 0x44, 0x72, 0x6f, 0x70, 0x43, 0x6f, 0x75, 0x6e, - 0x74, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x44, - 0x0a, 0x1f, 0x61, 0x74, 0x65, 0x5f, 0x69, 0x70, 0x76, 0x36, 0x5f, 0x66, 0x6c, 0x6f, 0x77, 0x5f, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x68, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x61, 0x74, 0x65, 0x49, 0x70, 0x76, 0x36, - 0x46, 0x6c, 0x6f, 0x77, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x25, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x74, 0x69, 0x6d, - 0x65, 0x72, 0x73, 0x5f, 0x63, 0x73, 0x6e, 0x70, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x69, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x21, 0x69, 0x73, 0x69, 0x73, 0x54, 0x69, 0x6d, 0x65, 0x72, 0x73, 0x43, - 0x73, 0x6e, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x55, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x71, 0x0a, 0x37, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x5f, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x66, 0x72, 0x6f, 0x6d, 0x5f, - 0x61, 0x72, 0x65, 0x61, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x6a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x30, 0x69, 0x73, 0x69, 0x73, 0x43, 0x6f, 0x75, - 0x6e, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x44, 0x72, 0x6f, 0x70, 0x46, 0x72, 0x6f, 0x6d, 0x41, 0x72, 0x65, 0x61, 0x73, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x25, 0x69, 0x73, 0x69, - 0x73, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x70, 0x61, 0x72, 0x74, 0x5f, 0x63, - 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0x6b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x69, 0x73, 0x69, 0x73, 0x43, 0x6f, - 0x75, 0x6e, 0x74, 0x65, 0x72, 0x50, 0x61, 0x72, 0x74, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x73, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4c, 0x0a, 0x22, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x5f, 0x74, 0x68, 0x72, 0x65, 0x73, - 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x6c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x20, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x73, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x20, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x5f, - 0x6d, 0x6f, 0x64, 0x65, 0x5f, 0x72, 0x61, 0x77, 0x5f, 0x67, 0x6e, 0x6d, 0x69, 0x18, 0x6d, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1c, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4c, 0x6f, - 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x4d, 0x6f, 0x64, 0x65, 0x52, 0x61, 0x77, 0x47, 0x6e, 0x6d, - 0x69, 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x74, 0x63, 0x70, 0x5f, 0x6e, 0x65, - 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x6d, 0x73, 0x73, 0x5f, 0x63, 0x68, 0x65, - 0x63, 0x6b, 0x18, 0x6e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x63, - 0x70, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, 0x64, 0x4d, 0x73, 0x73, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x12, 0x4c, 0x0a, 0x23, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6c, 0x73, 0x70, 0x5f, - 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x73, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x6f, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x1f, 0x69, 0x73, 0x69, 0x73, 0x4c, 0x73, 0x70, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, - 0x61, 0x4c, 0x65, 0x61, 0x66, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x31, 0x0a, 0x15, 0x71, 0x6f, 0x73, 0x5f, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x5f, 0x69, 0x64, 0x18, 0x70, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x12, 0x71, 0x6f, 0x73, 0x51, 0x75, 0x65, 0x75, 0x65, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x73, 0x49, 0x64, 0x12, 0x50, 0x0a, 0x25, 0x71, 0x6f, 0x73, 0x5f, 0x62, 0x75, 0x66, 0x66, - 0x65, 0x72, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x72, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x21, 0x71, 0x6f, 0x73, 0x42, 0x75, 0x66, 0x66, 0x65, 0x72, 0x41, 0x6c, - 0x6c, 0x6f, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, - 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, 0x66, 0x0a, 0x31, 0x62, 0x67, 0x70, 0x5f, 0x67, 0x6c, - 0x6f, 0x62, 0x61, 0x6c, 0x5f, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x6e, 0x65, - 0x78, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x65, 0x6e, 0x63, 0x6f, 0x64, 0x69, 0x6e, 0x67, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x73, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x2b, 0x62, 0x67, 0x70, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x45, 0x78, 0x74, 0x65, - 0x6e, 0x64, 0x65, 0x64, 0x4e, 0x65, 0x78, 0x74, 0x48, 0x6f, 0x70, 0x45, 0x6e, 0x63, 0x6f, 0x64, - 0x69, 0x6e, 0x67, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x31, - 0x0a, 0x15, 0x62, 0x67, 0x70, 0x5f, 0x6c, 0x6c, 0x67, 0x72, 0x5f, 0x6f, 0x63, 0x5f, 0x75, 0x6e, - 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, 0x64, 0x18, 0x74, 0x20, 0x01, 0x28, 0x08, 0x52, 0x12, 0x62, - 0x67, 0x70, 0x4c, 0x6c, 0x67, 0x72, 0x4f, 0x63, 0x55, 0x6e, 0x64, 0x65, 0x66, 0x69, 0x6e, 0x65, - 0x64, 0x12, 0x41, 0x0a, 0x1d, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0x75, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x50, 0x61, 0x74, 0x68, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x1e, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x76, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x74, 0x75, - 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x50, 0x61, 0x74, 0x68, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x51, 0x0a, 0x26, 0x65, 0x63, 0x6e, - 0x5f, 0x73, 0x61, 0x6d, 0x65, 0x5f, 0x6d, 0x69, 0x6e, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x74, 0x68, - 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, 0x64, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0x77, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x65, 0x63, 0x6e, 0x53, 0x61, - 0x6d, 0x65, 0x4d, 0x69, 0x6e, 0x4d, 0x61, 0x78, 0x54, 0x68, 0x72, 0x65, 0x73, 0x68, 0x6f, 0x6c, - 0x64, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x41, 0x0a, 0x1d, - 0x71, 0x6f, 0x73, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0x78, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1a, 0x71, 0x6f, 0x73, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, - 0x72, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x12, - 0x48, 0x0a, 0x21, 0x71, 0x6f, 0x73, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x77, 0x65, 0x69, 0x67, 0x68, - 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0x79, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x71, 0x6f, 0x73, 0x53, - 0x65, 0x74, 0x57, 0x65, 0x69, 0x67, 0x68, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x42, 0x0a, 0x1e, 0x71, 0x6f, 0x73, - 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x7a, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x1a, 0x71, 0x6f, 0x73, 0x47, 0x65, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x50, 0x61, - 0x74, 0x68, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2c, 0x0a, - 0x12, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6c, 0x65, 0x76, 0x65, 0x6c, 0x5f, 0x65, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x64, 0x18, 0x7b, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x69, 0x73, 0x69, 0x73, 0x4c, - 0x65, 0x76, 0x65, 0x6c, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x64, 0x12, 0x48, 0x0a, 0x21, 0x69, - 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x5f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x5f, 0x66, 0x6f, 0x72, 0x6d, 0x61, 0x74, - 0x18, 0x7c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, - 0x65, 0x52, 0x65, 0x66, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x49, 0x64, 0x46, - 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x12, 0x47, 0x0a, 0x20, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, - 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x7d, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x1d, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x4c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x6f, 0x70, 0x62, - 0x61, 0x63, 0x6b, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4d, - 0x0a, 0x24, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x70, 0x6c, 0x71, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, - 0x66, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x7e, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x73, 0x6b, - 0x69, 0x70, 0x50, 0x6c, 0x71, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4f, 0x70, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x4a, 0x0a, - 0x22, 0x62, 0x67, 0x70, 0x5f, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x70, 0x72, - 0x65, 0x66, 0x69, 0x78, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x72, 0x65, 0x63, 0x65, 0x69, - 0x76, 0x65, 0x64, 0x18, 0x7f, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x62, 0x67, 0x70, 0x45, 0x78, - 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x4c, 0x69, 0x6d, 0x69, - 0x74, 0x52, 0x65, 0x63, 0x65, 0x69, 0x76, 0x65, 0x64, 0x12, 0x58, 0x0a, 0x29, 0x62, 0x67, 0x70, - 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x6f, 0x63, 0x5f, 0x6d, 0x61, 0x78, 0x5f, - 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, - 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x80, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x62, - 0x67, 0x70, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x4f, 0x63, 0x4d, 0x61, 0x78, 0x50, 0x72, - 0x65, 0x66, 0x69, 0x78, 0x65, 0x73, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x52, 0x0a, 0x26, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x62, 0x67, 0x70, 0x5f, - 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x5f, 0x77, 0x69, - 0x74, 0x68, 0x6f, 0x75, 0x74, 0x5f, 0x61, 0x66, 0x69, 0x73, 0x61, 0x66, 0x69, 0x18, 0x81, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x67, 0x70, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x57, 0x69, 0x74, 0x68, 0x6f, 0x75, 0x74, - 0x41, 0x66, 0x69, 0x73, 0x61, 0x66, 0x69, 0x12, 0x62, 0x0a, 0x2e, 0x6d, 0x69, 0x73, 0x6d, 0x61, - 0x74, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x68, 0x61, 0x72, 0x64, 0x77, 0x61, 0x72, 0x65, 0x5f, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x69, 0x6e, 0x5f, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x18, 0x82, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x29, 0x6d, 0x69, 0x73, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x65, 0x64, 0x48, 0x61, 0x72, 0x64, - 0x77, 0x61, 0x72, 0x65, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, - 0x49, 0x6e, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x12, 0x5d, 0x0a, 0x2b, 0x67, - 0x6e, 0x6f, 0x69, 0x5f, 0x73, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, - 0x5f, 0x72, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x84, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x27, 0x67, 0x6e, 0x6f, 0x69, 0x53, 0x75, 0x62, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, - 0x65, 0x6e, 0x74, 0x52, 0x65, 0x62, 0x6f, 0x6f, 0x74, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x1f, 0x73, 0x6b, - 0x69, 0x70, 0x5f, 0x6e, 0x6f, 0x6e, 0x5f, 0x62, 0x67, 0x70, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, - 0x5f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x85, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x73, 0x6b, 0x69, 0x70, 0x4e, 0x6f, 0x6e, 0x42, 0x67, 0x70, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x43, 0x68, 0x65, 0x63, 0x6b, - 0x12, 0x55, 0x0a, 0x27, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, - 0x73, 0x74, 0x79, 0x6c, 0x65, 0x5f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x86, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x23, 0x69, 0x73, 0x69, 0x73, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x53, 0x74, - 0x79, 0x6c, 0x65, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x55, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x2f, 0x73, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x68, 0x6f, 0x70, - 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x72, 0x65, 0x66, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x87, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x29, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x4e, 0x65, - 0x78, 0x74, 0x48, 0x6f, 0x70, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x52, 0x65, - 0x66, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x3a, 0x0a, 0x19, - 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x6e, 0x65, 0x78, 0x74, - 0x68, 0x6f, 0x70, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x88, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x16, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x4e, 0x65, 0x78, 0x74, - 0x68, 0x6f, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x12, 0x5f, 0x0a, 0x2c, 0x69, 0x70, 0x76, 0x36, - 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x8a, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x28, 0x69, 0x70, 0x76, 0x36, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x41, 0x64, 0x76, 0x65, 0x72, - 0x74, 0x69, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x5d, 0x0a, 0x2b, 0x70, 0x72, 0x65, - 0x66, 0x69, 0x78, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x63, 0x65, 0x65, 0x64, - 0x65, 0x64, 0x5f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x8b, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x27, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x45, 0x78, 0x63, 0x65, - 0x65, 0x64, 0x65, 0x64, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x55, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x1e, 0x73, 0x6b, 0x69, 0x70, - 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6d, - 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x61, 0x73, 0x18, 0x8c, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x1a, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x41, 0x6c, - 0x6c, 0x6f, 0x77, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x41, 0x73, 0x12, 0x4b, 0x0a, - 0x22, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x64, 0x65, 0x63, 0x61, 0x70, 0x5f, 0x6d, 0x69, 0x78, - 0x65, 0x64, 0x5f, 0x70, 0x6c, 0x65, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0x8f, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x67, 0x72, 0x69, 0x62, - 0x69, 0x44, 0x65, 0x63, 0x61, 0x70, 0x4d, 0x69, 0x78, 0x65, 0x64, 0x50, 0x6c, 0x65, 0x6e, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x73, 0x6b, - 0x69, 0x70, 0x5f, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6c, 0x65, 0x76, 0x65, - 0x6c, 0x18, 0x90, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x10, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x73, - 0x69, 0x73, 0x53, 0x65, 0x74, 0x4c, 0x65, 0x76, 0x65, 0x6c, 0x12, 0x44, 0x0a, 0x1f, 0x73, 0x6b, - 0x69, 0x70, 0x5f, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x72, - 0x69, 0x63, 0x5f, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x91, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x73, 0x6b, 0x69, 0x70, 0x49, 0x73, 0x69, 0x73, 0x53, 0x65, - 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x53, 0x74, 0x79, 0x6c, 0x65, 0x54, 0x79, 0x70, 0x65, - 0x12, 0x40, 0x0a, 0x1d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x70, 0x5f, - 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x18, 0x92, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x65, - 0x74, 0x52, 0x70, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x53, 0x65, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x55, 0x0a, 0x27, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, - 0x6e, 0x67, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, - 0x63, 0x5f, 0x70, 0x72, 0x6f, 0x70, 0x61, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x93, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x50, 0x72, - 0x6f, 0x70, 0x61, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x62, 0x0a, 0x2e, 0x62, 0x67, 0x70, - 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x6d, 0x61, 0x74, 0x63, - 0x68, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x94, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x29, 0x62, 0x67, 0x70, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x53, - 0x65, 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x41, 0x0a, - 0x1d, 0x70, 0x66, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x5f, 0x6d, 0x61, 0x74, 0x63, - 0x68, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x18, 0x95, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x19, 0x70, 0x66, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, - 0x4d, 0x61, 0x74, 0x63, 0x68, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x75, 0x6c, 0x65, - 0x12, 0x67, 0x0a, 0x31, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x72, 0x74, - 0x5f, 0x74, 0x6f, 0x5f, 0x6f, 0x70, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, - 0x6e, 0x65, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x6d, 0x61, - 0x70, 0x70, 0x69, 0x6e, 0x67, 0x18, 0x96, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2b, 0x6d, 0x69, - 0x73, 0x73, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x72, 0x74, 0x54, 0x6f, 0x4f, 0x70, 0x74, 0x69, 0x63, - 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x4d, 0x61, 0x70, 0x70, 0x69, 0x6e, 0x67, 0x12, 0x2b, 0x0a, 0x11, 0x73, 0x6b, 0x69, - 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x5f, 0x6f, 0x70, 0x18, 0x97, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0f, 0x73, 0x6b, 0x69, 0x70, 0x43, 0x6f, 0x6e, 0x74, 0x61, - 0x69, 0x6e, 0x65, 0x72, 0x4f, 0x70, 0x12, 0x51, 0x0a, 0x25, 0x72, 0x65, 0x6f, 0x72, 0x64, 0x65, - 0x72, 0x5f, 0x63, 0x61, 0x6c, 0x6c, 0x73, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x76, 0x65, 0x6e, 0x64, - 0x6f, 0x72, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x74, 0x79, 0x18, - 0x98, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x72, 0x65, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x43, - 0x61, 0x6c, 0x6c, 0x73, 0x46, 0x6f, 0x72, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x43, 0x6f, 0x6d, - 0x70, 0x61, 0x74, 0x69, 0x62, 0x69, 0x6c, 0x74, 0x79, 0x12, 0x44, 0x0a, 0x1f, 0x61, 0x64, 0x64, - 0x5f, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x62, 0x61, 0x73, 0x65, 0x5f, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x5f, 0x76, 0x69, 0x61, 0x5f, 0x63, 0x6c, 0x69, 0x18, 0x99, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1a, 0x61, 0x64, 0x64, 0x4d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x42, - 0x61, 0x73, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x69, 0x61, 0x43, 0x6c, 0x69, 0x12, - 0x33, 0x0a, 0x15, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x6d, 0x61, 0x63, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x5f, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x18, 0x9a, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x13, 0x73, 0x6b, 0x69, 0x70, 0x4d, 0x61, 0x63, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x43, - 0x68, 0x65, 0x63, 0x6b, 0x12, 0x3d, 0x0a, 0x1b, 0x62, 0x67, 0x70, 0x5f, 0x72, 0x69, 0x62, 0x5f, - 0x6f, 0x63, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0x9b, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x62, 0x67, 0x70, 0x52, - 0x69, 0x62, 0x4f, 0x63, 0x50, 0x61, 0x74, 0x68, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x14, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x70, 0x72, 0x65, 0x66, - 0x69, 0x78, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0x9c, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x11, 0x73, 0x6b, 0x69, 0x70, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x53, 0x65, - 0x74, 0x4d, 0x6f, 0x64, 0x65, 0x12, 0x38, 0x0a, 0x18, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x5f, 0x61, 0x73, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, - 0x65, 0x18, 0x9d, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x65, 0x74, 0x4d, 0x65, 0x74, - 0x72, 0x69, 0x63, 0x41, 0x73, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, - 0x72, 0x0a, 0x38, 0x69, 0x70, 0x76, 0x36, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x6e, - 0x65, 0x78, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, - 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x61, 0x72, 0x70, 0x18, 0x9e, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x2f, 0x69, 0x70, 0x76, 0x36, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, 0x6f, - 0x75, 0x74, 0x65, 0x57, 0x69, 0x74, 0x68, 0x49, 0x70, 0x76, 0x34, 0x4e, 0x65, 0x78, 0x74, 0x48, - 0x6f, 0x70, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x73, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, - 0x41, 0x72, 0x70, 0x12, 0x50, 0x0a, 0x25, 0x70, 0x66, 0x5f, 0x72, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x5f, 0x73, 0x65, 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x6f, 0x72, 0x64, - 0x65, 0x72, 0x5f, 0x70, 0x62, 0x72, 0x5f, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x18, 0x9f, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x20, 0x70, 0x66, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x53, 0x65, - 0x71, 0x75, 0x65, 0x6e, 0x74, 0x69, 0x61, 0x6c, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x50, 0x62, 0x72, - 0x52, 0x75, 0x6c, 0x65, 0x73, 0x12, 0x61, 0x0a, 0x2e, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, - 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x65, - 0x78, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x74, 0x65, - 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x18, 0xa0, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x28, - 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, 0x6f, 0x75, - 0x74, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x48, 0x6f, 0x70, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x54, - 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x58, 0x0a, 0x29, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x72, 0x65, - 0x63, 0x75, 0x72, 0x73, 0x65, 0x18, 0xa1, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x4e, 0x65, 0x78, 0x74, 0x48, 0x6f, 0x70, 0x52, 0x65, 0x63, 0x75, 0x72, - 0x73, 0x65, 0x12, 0x5d, 0x0a, 0x2c, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x5f, - 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x68, 0x6f, 0x70, 0x5f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, - 0x72, 0x79, 0x18, 0xa2, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x26, 0x6d, 0x69, 0x73, 0x73, 0x69, - 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x44, 0x72, 0x6f, - 0x70, 0x4e, 0x65, 0x78, 0x74, 0x48, 0x6f, 0x70, 0x54, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, - 0x79, 0x12, 0x73, 0x0a, 0x37, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x7a, 0x72, 0x5f, - 0x6f, 0x70, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, - 0x74, 0x75, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, - 0x72, 0x73, 0x5f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x18, 0xa3, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x31, 0x6d, 0x69, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5a, 0x72, 0x4f, 0x70, - 0x74, 0x69, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x75, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x54, 0x65, 0x6c, - 0x65, 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x46, 0x0a, 0x1f, 0x70, 0x6c, 0x71, 0x5f, 0x72, 0x65, - 0x66, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x73, 0x5f, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xa4, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x1c, 0x70, 0x6c, 0x71, 0x52, 0x65, 0x66, 0x6c, 0x65, 0x63, 0x74, 0x6f, 0x72, 0x53, 0x74, - 0x61, 0x74, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4b, - 0x0a, 0x22, 0x70, 0x6c, 0x71, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, - 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x6d, 0x61, 0x78, - 0x5f, 0x6d, 0x74, 0x75, 0x18, 0xa5, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x1e, 0x70, 0x6c, 0x71, - 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x4d, 0x61, 0x78, 0x4d, 0x74, 0x75, 0x12, 0x4b, 0x0a, 0x22, 0x70, - 0x6c, 0x71, 0x5f, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x5f, 0x63, 0x61, 0x70, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x70, 0x70, - 0x73, 0x18, 0xa6, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x1e, 0x70, 0x6c, 0x71, 0x47, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, - 0x65, 0x73, 0x4d, 0x61, 0x78, 0x50, 0x70, 0x73, 0x12, 0x57, 0x0a, 0x28, 0x62, 0x67, 0x70, 0x5f, - 0x65, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, - 0x74, 0x79, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0xa7, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x62, 0x67, 0x70, - 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, - 0x79, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x4b, 0x0a, 0x22, 0x62, 0x67, 0x70, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, - 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xa8, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, - 0x62, 0x67, 0x70, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x52, - 0x65, 0x66, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x1c, - 0x0a, 0x09, 0x72, 0x69, 0x62, 0x5f, 0x77, 0x65, 0x63, 0x6d, 0x70, 0x18, 0xa9, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x08, 0x72, 0x69, 0x62, 0x57, 0x65, 0x63, 0x6d, 0x70, 0x12, 0x43, 0x0a, 0x1d, - 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xaa, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, 0x6e, 0x6e, 0x65, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x46, 0x0a, 0x20, 0x75, 0x73, 0x65, 0x5f, 0x76, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x5f, - 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x63, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0xab, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x75, 0x73, - 0x65, 0x56, 0x65, 0x6e, 0x64, 0x6f, 0x72, 0x4e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x54, 0x61, 0x67, - 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3f, 0x0a, 0x1c, 0x73, 0x6b, 0x69, - 0x70, 0x5f, 0x62, 0x67, 0x70, 0x5f, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, - 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0xac, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x18, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x67, 0x70, 0x53, 0x65, 0x6e, 0x64, 0x43, 0x6f, 0x6d, - 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x54, 0x79, 0x70, 0x65, 0x12, 0x5e, 0x0a, 0x2c, 0x62, 0x67, - 0x70, 0x5f, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x63, 0x6f, - 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xae, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x27, 0x62, 0x67, 0x70, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x65, 0x74, - 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x11, 0x73, 0x65, - 0x74, 0x5f, 0x6e, 0x6f, 0x5f, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, - 0xaf, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0e, 0x73, 0x65, 0x74, 0x4e, 0x6f, 0x50, 0x65, 0x65, - 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x46, 0x0a, 0x20, 0x62, 0x67, 0x70, 0x5f, 0x63, 0x6f, - 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x69, - 0x73, 0x5f, 0x61, 0x5f, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x18, 0xb0, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x1b, 0x62, 0x67, 0x70, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x49, 0x73, 0x41, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x59, - 0x0a, 0x2a, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x6f, - 0x75, 0x74, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x69, 0x70, 0x76, 0x36, 0x5f, 0x6e, 0x68, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xb1, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x24, 0x69, 0x70, 0x76, 0x34, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, - 0x6f, 0x75, 0x74, 0x65, 0x57, 0x69, 0x74, 0x68, 0x49, 0x70, 0x76, 0x36, 0x4e, 0x68, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, 0x0a, 0x2a, 0x69, 0x70, 0x76, - 0x36, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x77, - 0x69, 0x74, 0x68, 0x5f, 0x69, 0x70, 0x76, 0x34, 0x5f, 0x6e, 0x68, 0x5f, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xb2, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, - 0x69, 0x70, 0x76, 0x36, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x57, - 0x69, 0x74, 0x68, 0x49, 0x70, 0x76, 0x34, 0x4e, 0x68, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x39, 0x0a, 0x19, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, - 0x6f, 0x75, 0x74, 0x65, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x6e, - 0x68, 0x18, 0xb3, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x15, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, - 0x52, 0x6f, 0x75, 0x74, 0x65, 0x57, 0x69, 0x74, 0x68, 0x44, 0x72, 0x6f, 0x70, 0x4e, 0x68, 0x12, - 0x49, 0x0a, 0x21, 0x73, 0x74, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x5f, - 0x77, 0x69, 0x74, 0x68, 0x5f, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x6d, 0x65, - 0x74, 0x72, 0x69, 0x63, 0x18, 0xb4, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x73, 0x74, 0x61, - 0x74, 0x69, 0x63, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x57, 0x69, 0x74, 0x68, 0x45, 0x78, 0x70, 0x6c, - 0x69, 0x63, 0x69, 0x74, 0x4d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x12, 0x44, 0x0a, 0x1e, 0x62, 0x67, - 0x70, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xb5, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1b, 0x62, 0x67, 0x70, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x12, 0x4a, 0x0a, 0x22, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x5f, 0x62, 0x67, 0x70, 0x5f, 0x6f, 0x6e, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, - 0x6c, 0x74, 0x5f, 0x76, 0x72, 0x66, 0x18, 0xb6, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x65, - 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x42, 0x67, 0x70, - 0x4f, 0x6e, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x72, 0x66, 0x12, 0x45, 0x0a, 0x1f, - 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x74, - 0x61, 0x67, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x6d, 0x62, 0x65, 0x64, 0x64, 0x65, 0x64, 0x18, - 0xb7, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x50, - 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x54, 0x61, 0x67, 0x53, 0x65, 0x74, 0x45, 0x6d, 0x62, 0x65, 0x64, - 0x64, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x26, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x61, 0x66, 0x69, 0x5f, - 0x73, 0x61, 0x66, 0x69, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x62, 0x67, - 0x70, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x61, 0x73, 0x18, 0xb8, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, 0x73, 0x6b, 0x69, 0x70, 0x41, 0x66, 0x69, 0x53, 0x61, 0x66, - 0x69, 0x50, 0x61, 0x74, 0x68, 0x46, 0x6f, 0x72, 0x42, 0x67, 0x70, 0x4d, 0x75, 0x6c, 0x74, 0x69, - 0x70, 0x6c, 0x65, 0x41, 0x73, 0x12, 0x4c, 0x0a, 0x22, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, - 0x74, 0x79, 0x5f, 0x6d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x5f, 0x72, 0x65, 0x67, 0x65, 0x78, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xb9, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x1f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x4d, 0x65, 0x6d, - 0x62, 0x65, 0x72, 0x52, 0x65, 0x67, 0x65, 0x78, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x12, 0x46, 0x0a, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x5f, 0x61, 0x74, 0x74, 0x61, 0x63, 0x68, 0x65, 0x64, 0x5f, 0x74, 0x6f, 0x5f, 0x61, - 0x6c, 0x6c, 0x5f, 0x61, 0x66, 0x69, 0x73, 0x18, 0xba, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, - 0x73, 0x61, 0x6d, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x41, 0x74, 0x74, 0x61, 0x63, 0x68, - 0x65, 0x64, 0x54, 0x6f, 0x41, 0x6c, 0x6c, 0x41, 0x66, 0x69, 0x73, 0x12, 0x49, 0x0a, 0x21, 0x73, - 0x6b, 0x69, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x18, 0xbb, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x73, 0x6b, 0x69, 0x70, 0x53, 0x65, 0x74, - 0x74, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x46, 0x6f, 0x72, - 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x12, 0x42, 0x0a, 0x1d, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x63, - 0x68, 0x65, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, - 0x65, 0x5f, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0xbc, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, - 0x73, 0x6b, 0x69, 0x70, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x69, 0x6e, 0x67, 0x41, 0x74, 0x74, 0x72, - 0x69, 0x62, 0x75, 0x74, 0x65, 0x49, 0x6e, 0x64, 0x65, 0x78, 0x12, 0x55, 0x0a, 0x27, 0x66, 0x6c, - 0x61, 0x74, 0x74, 0x65, 0x6e, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x77, 0x69, 0x74, - 0x68, 0x5f, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x6d, 0x65, 0x6e, 0x74, 0x73, 0x18, 0xbd, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x66, 0x6c, - 0x61, 0x74, 0x74, 0x65, 0x6e, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x57, 0x69, 0x74, 0x68, 0x4d, - 0x75, 0x6c, 0x74, 0x69, 0x70, 0x6c, 0x65, 0x53, 0x74, 0x61, 0x74, 0x65, 0x6d, 0x65, 0x6e, 0x74, - 0x73, 0x12, 0x48, 0x0a, 0x20, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x72, 0x6f, 0x75, - 0x74, 0x65, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xbe, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x64, 0x65, - 0x66, 0x61, 0x75, 0x6c, 0x74, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x16, 0x73, - 0x6c, 0x61, 0x61, 0x63, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x5f, 0x6c, 0x65, 0x6e, 0x67, - 0x74, 0x68, 0x31, 0x32, 0x38, 0x18, 0xbf, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x73, 0x6c, - 0x61, 0x61, 0x63, 0x50, 0x72, 0x65, 0x66, 0x69, 0x78, 0x4c, 0x65, 0x6e, 0x67, 0x74, 0x68, 0x31, - 0x32, 0x38, 0x12, 0x4d, 0x0a, 0x23, 0x62, 0x67, 0x70, 0x5f, 0x6d, 0x61, 0x78, 0x5f, 0x6d, 0x75, - 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x73, 0x5f, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xc0, 0x01, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x1f, 0x62, 0x67, 0x70, 0x4d, 0x61, 0x78, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, - 0x68, 0x50, 0x61, 0x74, 0x68, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x59, 0x0a, 0x29, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x6e, 0x65, 0x69, 0x67, 0x68, - 0x62, 0x6f, 0x72, 0x5f, 0x6f, 0x72, 0x5f, 0x61, 0x66, 0x69, 0x73, 0x61, 0x66, 0x69, 0x18, 0xc1, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x6d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x4e, 0x65, 0x69, 0x67, 0x68, - 0x62, 0x6f, 0x72, 0x4f, 0x72, 0x41, 0x66, 0x69, 0x73, 0x61, 0x66, 0x69, 0x12, 0x35, 0x0a, 0x16, - 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xc2, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x6d, - 0x6f, 0x64, 0x65, 0x6c, 0x4e, 0x61, 0x6d, 0x65, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x12, 0x65, 0x0a, 0x2f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, - 0x5f, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x72, 0x65, 0x64, 0x69, - 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xc3, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2b, 0x63, - 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x4d, 0x61, 0x74, 0x63, 0x68, 0x57, 0x69, 0x74, - 0x68, 0x52, 0x65, 0x64, 0x69, 0x73, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x69, 0x6f, 0x6e, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x6a, 0x0a, 0x32, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x70, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x61, - 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x5f, 0x63, 0x6f, 0x6d, 0x70, 0x6f, - 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0xc4, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2d, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, - 0x50, 0x6f, 0x73, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x41, 0x6e, 0x64, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x57, 0x0a, 0x29, 0x65, 0x6e, 0x63, 0x61, 0x70, 0x5f, - 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x73, 0x68, 0x75, 0x74, 0x5f, 0x62, 0x61, 0x63, 0x6b, - 0x75, 0x70, 0x5f, 0x6e, 0x68, 0x67, 0x5f, 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x74, 0x72, 0x61, 0x66, - 0x66, 0x69, 0x63, 0x18, 0xc5, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x65, 0x6e, 0x63, 0x61, - 0x70, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x53, 0x68, 0x75, 0x74, 0x42, 0x61, 0x63, 0x6b, 0x75, - 0x70, 0x4e, 0x68, 0x67, 0x5a, 0x65, 0x72, 0x6f, 0x54, 0x72, 0x61, 0x66, 0x66, 0x69, 0x63, 0x12, - 0x25, 0x0a, 0x0e, 0x6d, 0x61, 0x78, 0x5f, 0x65, 0x63, 0x6d, 0x70, 0x5f, 0x70, 0x61, 0x74, 0x68, - 0x73, 0x18, 0xc6, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x6d, 0x61, 0x78, 0x45, 0x63, 0x6d, - 0x70, 0x50, 0x61, 0x74, 0x68, 0x73, 0x12, 0x35, 0x0a, 0x16, 0x77, 0x65, 0x63, 0x6d, 0x70, 0x5f, - 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0xc7, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x77, 0x65, 0x63, 0x6d, 0x70, 0x41, 0x75, - 0x74, 0x6f, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4e, 0x0a, - 0x23, 0x72, 0x6f, 0x75, 0x74, 0x69, 0x6e, 0x67, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, - 0x63, 0x68, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0xc8, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x20, 0x72, 0x6f, 0x75, - 0x74, 0x69, 0x6e, 0x67, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x43, 0x68, 0x61, 0x69, 0x6e, 0x69, - 0x6e, 0x67, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, - 0x16, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x6c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x5f, 0x72, - 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x64, 0x18, 0xc9, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, - 0x69, 0x73, 0x69, 0x73, 0x4c, 0x6f, 0x6f, 0x70, 0x62, 0x61, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x27, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, - 0x5f, 0x65, 0x63, 0x6d, 0x70, 0x5f, 0x66, 0x69, 0x78, 0x65, 0x64, 0x5f, 0x70, 0x61, 0x63, 0x6b, - 0x65, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, - 0xca, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x77, 0x65, 0x69, 0x67, 0x68, 0x74, 0x65, 0x64, - 0x45, 0x63, 0x6d, 0x70, 0x46, 0x69, 0x78, 0x65, 0x64, 0x50, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x56, - 0x65, 0x72, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3a, 0x0a, 0x19, 0x6f, - 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, - 0x6e, 0x68, 0x5f, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x18, 0xcb, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x16, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, - 0x4e, 0x68, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x12, 0x53, 0x0a, 0x26, 0x62, 0x67, 0x70, 0x5f, 0x65, - 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, - 0x79, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0xcc, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x62, 0x67, 0x70, 0x45, 0x78, 0x74, - 0x65, 0x6e, 0x64, 0x65, 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x53, 0x65, - 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, 0x0a, 0x2a, - 0x62, 0x67, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x65, 0x78, 0x74, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, - 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x72, 0x65, 0x66, 0x73, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xcd, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x24, 0x62, 0x67, 0x70, 0x53, 0x65, 0x74, 0x45, 0x78, 0x74, 0x43, 0x6f, 0x6d, 0x6d, - 0x75, 0x6e, 0x69, 0x74, 0x79, 0x53, 0x65, 0x74, 0x52, 0x65, 0x66, 0x73, 0x55, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x51, 0x0a, 0x25, 0x62, 0x67, 0x70, 0x5f, 0x64, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x5f, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x62, 0x61, 0x6e, 0x64, 0x77, - 0x69, 0x64, 0x74, 0x68, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0xce, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x62, 0x67, 0x70, 0x44, 0x65, 0x6c, 0x65, - 0x74, 0x65, 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x61, 0x6e, 0x64, 0x77, 0x69, 0x64, 0x74, 0x68, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4f, 0x0a, 0x24, 0x71, 0x6f, - 0x73, 0x5f, 0x69, 0x6e, 0x71, 0x75, 0x65, 0x75, 0x65, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x5f, 0x63, - 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0xcf, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x20, 0x71, 0x6f, 0x73, 0x49, 0x6e, - 0x71, 0x75, 0x65, 0x75, 0x65, 0x44, 0x72, 0x6f, 0x70, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x53, 0x0a, 0x26, 0x62, - 0x67, 0x70, 0x5f, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x5f, 0x65, 0x78, 0x74, 0x65, - 0x6e, 0x64, 0x65, 0x64, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x65, - 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x18, 0xd0, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x62, 0x67, - 0x70, 0x45, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x64, 0x65, - 0x64, 0x43, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x45, 0x6e, 0x61, 0x62, 0x6c, 0x65, - 0x12, 0x4d, 0x0a, 0x23, 0x6d, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x74, 0x61, 0x67, 0x5f, 0x73, 0x65, - 0x74, 0x5f, 0x63, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd1, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1f, - 0x6d, 0x61, 0x74, 0x63, 0x68, 0x54, 0x61, 0x67, 0x53, 0x65, 0x74, 0x43, 0x6f, 0x6e, 0x64, 0x69, - 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, - 0x4c, 0x0a, 0x23, 0x70, 0x65, 0x65, 0x72, 0x5f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x5f, 0x64, 0x65, - 0x66, 0x5f, 0x65, 0x62, 0x67, 0x70, 0x5f, 0x76, 0x72, 0x66, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd2, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x70, - 0x65, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x44, 0x65, 0x66, 0x45, 0x62, 0x67, 0x70, 0x56, - 0x72, 0x66, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x5a, 0x0a, - 0x2a, 0x72, 0x65, 0x64, 0x69, 0x73, 0x5f, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x65, 0x62, 0x67, 0x70, 0x5f, 0x76, 0x72, 0x66, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd3, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x25, 0x72, 0x65, 0x64, 0x69, 0x73, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, - 0x65, 0x64, 0x55, 0x6e, 0x64, 0x65, 0x72, 0x45, 0x62, 0x67, 0x70, 0x56, 0x72, 0x66, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x57, 0x0a, 0x2a, 0x62, 0x67, 0x70, - 0x5f, 0x61, 0x66, 0x69, 0x5f, 0x73, 0x61, 0x66, 0x69, 0x5f, 0x69, 0x6e, 0x5f, 0x64, 0x65, 0x66, - 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x6e, 0x69, 0x5f, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x5f, 0x6f, - 0x74, 0x68, 0x65, 0x72, 0x5f, 0x6e, 0x69, 0x18, 0xd4, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, - 0x62, 0x67, 0x70, 0x41, 0x66, 0x69, 0x53, 0x61, 0x66, 0x69, 0x49, 0x6e, 0x44, 0x65, 0x66, 0x61, - 0x75, 0x6c, 0x74, 0x4e, 0x69, 0x42, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x4f, 0x74, 0x68, 0x65, 0x72, - 0x4e, 0x69, 0x12, 0x57, 0x0a, 0x28, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x69, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x6f, 0x6c, 0x69, - 0x63, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd5, - 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x49, 0x6d, - 0x70, 0x6f, 0x72, 0x74, 0x45, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x79, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x63, 0x0a, 0x2e, 0x69, - 0x70, 0x76, 0x36, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x5f, 0x61, 0x64, 0x76, 0x65, 0x72, - 0x74, 0x69, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, - 0x6c, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd6, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x2a, 0x69, 0x70, 0x76, 0x36, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x72, - 0x41, 0x64, 0x76, 0x65, 0x72, 0x74, 0x69, 0x73, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x49, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x12, 0x4e, 0x0a, 0x24, 0x64, 0x65, 0x63, 0x61, 0x70, 0x5f, 0x6e, 0x68, 0x5f, 0x77, 0x69, 0x74, - 0x68, 0x5f, 0x6e, 0x65, 0x78, 0x74, 0x68, 0x6f, 0x70, 0x5f, 0x6e, 0x69, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd7, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x1f, 0x64, 0x65, 0x63, 0x61, 0x70, 0x4e, 0x68, 0x57, 0x69, 0x74, 0x68, 0x4e, 0x65, 0x78, 0x74, - 0x68, 0x6f, 0x70, 0x4e, 0x69, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x12, 0x48, 0x0a, 0x20, 0x63, 0x6f, 0x6d, 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x5f, 0x69, 0x6e, - 0x76, 0x65, 0x72, 0x74, 0x5f, 0x61, 0x6e, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0xd8, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x63, 0x6f, 0x6d, - 0x6d, 0x75, 0x6e, 0x69, 0x74, 0x79, 0x49, 0x6e, 0x76, 0x65, 0x72, 0x74, 0x41, 0x6e, 0x79, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x55, 0x0a, 0x27, 0x73, 0x66, - 0x6c, 0x6f, 0x77, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xd9, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, 0x73, 0x66, - 0x6c, 0x6f, 0x77, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x2e, 0x0a, 0x13, 0x6c, 0x69, 0x6e, 0x6b, 0x5f, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x5f, - 0x6d, 0x61, 0x73, 0x6b, 0x5f, 0x6c, 0x65, 0x6e, 0x18, 0xda, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x10, 0x6c, 0x69, 0x6e, 0x6b, 0x4c, 0x6f, 0x63, 0x61, 0x6c, 0x4d, 0x61, 0x73, 0x6b, 0x4c, 0x65, - 0x6e, 0x12, 0x62, 0x0a, 0x2e, 0x75, 0x73, 0x65, 0x5f, 0x70, 0x61, 0x72, 0x65, 0x6e, 0x74, 0x5f, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x74, 0x65, - 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x5f, 0x74, 0x65, 0x6c, 0x65, 0x6d, 0x65, - 0x74, 0x72, 0x79, 0x18, 0xdb, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x29, 0x75, 0x73, 0x65, 0x50, - 0x61, 0x72, 0x65, 0x6e, 0x74, 0x43, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x46, 0x6f, - 0x72, 0x54, 0x65, 0x6d, 0x70, 0x65, 0x72, 0x61, 0x74, 0x75, 0x72, 0x65, 0x54, 0x65, 0x6c, 0x65, - 0x6d, 0x65, 0x74, 0x72, 0x79, 0x12, 0x44, 0x0a, 0x1e, 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, - 0x6e, 0x74, 0x5f, 0x6d, 0x66, 0x67, 0x5f, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xdc, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, - 0x63, 0x6f, 0x6d, 0x70, 0x6f, 0x6e, 0x65, 0x6e, 0x74, 0x4d, 0x66, 0x67, 0x44, 0x61, 0x74, 0x65, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x40, 0x0a, 0x1c, 0x6f, - 0x74, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x74, 0x72, 0x69, 0x62, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xdd, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x19, 0x6f, 0x74, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x54, 0x72, - 0x69, 0x62, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x5b, 0x0a, - 0x2a, 0x65, 0x74, 0x68, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x69, 0x6e, 0x67, - 0x72, 0x65, 0x73, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xde, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x26, 0x65, 0x74, 0x68, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, - 0x67, 0x72, 0x65, 0x73, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x53, 0x0a, 0x26, 0x65, 0x74, - 0x68, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, - 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, - 0x72, 0x69, 0x6e, 0x67, 0x18, 0xdf, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x65, 0x74, 0x68, - 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, - 0x74, 0x43, 0x69, 0x73, 0x63, 0x6f, 0x4e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, - 0x4a, 0x0a, 0x21, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x63, 0x6f, 0x75, - 0x6e, 0x74, 0x65, 0x72, 0x73, 0x5f, 0x75, 0x70, 0x64, 0x61, 0x74, 0x65, 0x5f, 0x64, 0x65, 0x6c, - 0x61, 0x79, 0x65, 0x64, 0x18, 0xe0, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x55, 0x70, - 0x64, 0x61, 0x74, 0x65, 0x44, 0x65, 0x6c, 0x61, 0x79, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x1b, 0x63, - 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x72, 0x70, 0x63, 0x5f, 0x75, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xe1, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x18, 0x63, 0x68, 0x61, 0x73, 0x73, 0x69, 0x73, 0x47, 0x65, 0x74, 0x52, 0x70, 0x63, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x56, 0x0a, 0x28, 0x70, - 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x64, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x5f, 0x6c, 0x65, 0x61, 0x66, 0x5f, 0x72, 0x65, 0x66, 0x5f, 0x76, 0x61, 0x6c, - 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0xe2, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x23, - 0x70, 0x6f, 0x77, 0x65, 0x72, 0x44, 0x69, 0x73, 0x61, 0x62, 0x6c, 0x65, 0x45, 0x6e, 0x61, 0x62, - 0x6c, 0x65, 0x4c, 0x65, 0x61, 0x66, 0x52, 0x65, 0x66, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x46, 0x0a, 0x1f, 0x73, 0x73, 0x68, 0x5f, 0x73, 0x65, 0x72, 0x76, 0x65, - 0x72, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xe3, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x73, - 0x73, 0x68, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x73, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x41, 0x0a, 0x1c, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x5f, - 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xe4, 0x01, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x1a, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x6c, 0x4d, - 0x6f, 0x64, 0x65, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x51, - 0x0a, 0x26, 0x62, 0x67, 0x70, 0x5f, 0x73, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x5f, 0x69, 0x64, 0x6c, 0x65, 0x5f, 0x69, 0x6e, 0x5f, 0x70, 0x61, 0x73, 0x73, - 0x69, 0x76, 0x65, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x18, 0xe5, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x20, 0x62, 0x67, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, - 0x49, 0x64, 0x6c, 0x65, 0x49, 0x6e, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x4d, 0x6f, 0x64, - 0x65, 0x12, 0x45, 0x0a, 0x1f, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x6d, 0x75, 0x6c, 0x74, - 0x69, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x75, 0x6e, 0x64, 0x65, 0x72, 0x5f, 0x61, 0x66, 0x69, 0x5f, - 0x73, 0x61, 0x66, 0x69, 0x18, 0xe6, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, 0x65, 0x6e, 0x61, - 0x62, 0x6c, 0x65, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x70, 0x61, 0x74, 0x68, 0x55, 0x6e, 0x64, 0x65, - 0x72, 0x41, 0x66, 0x69, 0x53, 0x61, 0x66, 0x69, 0x12, 0x49, 0x0a, 0x21, 0x62, 0x67, 0x70, 0x5f, - 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x77, 0x6e, 0x61, 0x73, 0x5f, 0x64, 0x69, 0x66, 0x66, 0x5f, - 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xe7, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x1d, 0x62, 0x67, 0x70, 0x41, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x77, - 0x6e, 0x61, 0x73, 0x44, 0x69, 0x66, 0x66, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x56, 0x61, - 0x6c, 0x75, 0x65, 0x12, 0x53, 0x0a, 0x26, 0x6f, 0x74, 0x6e, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x5f, 0x63, 0x69, - 0x73, 0x63, 0x6f, 0x5f, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x18, 0xe8, 0x01, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x22, 0x6f, 0x74, 0x6e, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, - 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x43, 0x69, 0x73, 0x63, 0x6f, 0x4e, - 0x75, 0x6d, 0x62, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x12, 0x46, 0x0a, 0x20, 0x63, 0x69, 0x73, 0x63, - 0x6f, 0x5f, 0x70, 0x72, 0x65, 0x5f, 0x66, 0x65, 0x63, 0x5f, 0x62, 0x65, 0x72, 0x5f, 0x69, 0x6e, - 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0xe9, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1b, 0x63, 0x69, 0x73, 0x63, 0x6f, 0x50, 0x72, 0x65, 0x46, 0x65, 0x63, - 0x42, 0x65, 0x72, 0x49, 0x6e, 0x61, 0x63, 0x74, 0x69, 0x76, 0x65, 0x56, 0x61, 0x6c, 0x75, 0x65, - 0x12, 0x4c, 0x0a, 0x23, 0x62, 0x67, 0x70, 0x5f, 0x61, 0x66, 0x69, 0x5f, 0x73, 0x61, 0x66, 0x69, - 0x5f, 0x77, 0x69, 0x6c, 0x64, 0x63, 0x61, 0x72, 0x64, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xeb, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, - 0x62, 0x67, 0x70, 0x41, 0x66, 0x69, 0x53, 0x61, 0x66, 0x69, 0x57, 0x69, 0x6c, 0x64, 0x63, 0x61, - 0x72, 0x64, 0x4e, 0x6f, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x39, - 0x0a, 0x18, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x74, 0x61, 0x62, 0x6c, 0x65, 0x5f, 0x63, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0xec, 0x01, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x16, 0x65, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x54, 0x61, 0x62, 0x6c, 0x65, 0x43, 0x6f, - 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x0a, 0x13, 0x6e, 0x6f, 0x5f, - 0x7a, 0x65, 0x72, 0x6f, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0xed, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x6e, 0x6f, 0x5a, 0x65, 0x72, 0x6f, 0x53, - 0x75, 0x70, 0x70, 0x72, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x57, 0x0a, 0x28, 0x69, 0x73, - 0x69, 0x73, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x6c, 0x65, 0x76, - 0x65, 0x6c, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xee, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x24, 0x69, - 0x73, 0x69, 0x73, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4c, 0x65, 0x76, 0x65, - 0x6c, 0x50, 0x61, 0x73, 0x73, 0x69, 0x76, 0x65, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x12, 0x3c, 0x0a, 0x1a, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x64, 0x69, 0x73, 0x5f, - 0x73, 0x79, 0x73, 0x69, 0x64, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0xef, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x69, 0x73, 0x69, 0x73, 0x44, 0x69, - 0x73, 0x53, 0x79, 0x73, 0x69, 0x64, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x4e, 0x0a, 0x23, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xf0, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x20, 0x69, 0x73, 0x69, 0x73, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4f, 0x76, 0x65, - 0x72, 0x6c, 0x6f, 0x61, 0x64, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x3b, 0x0a, 0x1a, 0x62, 0x67, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x64, - 0x5f, 0x76, 0x37, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, - 0xf1, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x62, 0x67, 0x70, 0x53, 0x65, 0x74, 0x4d, 0x65, - 0x64, 0x56, 0x37, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x4f, - 0x0a, 0x24, 0x74, 0x63, 0x5f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x69, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x79, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xf2, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x20, 0x74, - 0x63, 0x44, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x50, 0x6f, - 0x6c, 0x69, 0x63, 0x79, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, - 0x4a, 0x0a, 0x21, 0x74, 0x63, 0x5f, 0x6d, 0x65, 0x74, 0x72, 0x69, 0x63, 0x5f, 0x70, 0x72, 0x6f, - 0x70, 0x61, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0xf3, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1e, 0x74, 0x63, 0x4d, - 0x65, 0x74, 0x72, 0x69, 0x63, 0x50, 0x72, 0x6f, 0x70, 0x61, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x50, 0x0a, 0x24, 0x74, - 0x63, 0x5f, 0x61, 0x74, 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x5f, 0x70, 0x72, 0x6f, 0x70, - 0x61, 0x67, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0xf4, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x21, 0x74, 0x63, 0x41, 0x74, - 0x74, 0x72, 0x69, 0x62, 0x75, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x70, 0x61, 0x67, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x3f, 0x0a, - 0x1b, 0x74, 0x63, 0x5f, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xf5, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x19, 0x74, 0x63, 0x53, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x3a, - 0x0a, 0x19, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x62, 0x67, 0x70, 0x5f, 0x69, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0xf6, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x16, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x42, 0x67, 0x70, 0x49, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x6e, 0x0a, 0x34, 0x63, 0x68, - 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x61, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x72, 0x61, 0x74, 0x65, 0x5f, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x5f, 0x70, 0x61, 0x72, 0x61, - 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0xf7, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2f, 0x63, 0x68, 0x61, 0x6e, 0x6e, - 0x65, 0x6c, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x61, 0x74, 0x65, - 0x43, 0x6c, 0x61, 0x73, 0x73, 0x50, 0x61, 0x72, 0x61, 0x6d, 0x65, 0x74, 0x65, 0x72, 0x73, 0x55, - 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x59, 0x0a, 0x29, 0x71, 0x6f, - 0x73, 0x5f, 0x73, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x5f, 0x70, 0x6f, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x5f, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xf8, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, - 0x71, 0x6f, 0x73, 0x53, 0x63, 0x68, 0x65, 0x64, 0x75, 0x6c, 0x65, 0x72, 0x49, 0x6e, 0x67, 0x72, - 0x65, 0x73, 0x73, 0x50, 0x6f, 0x6c, 0x69, 0x63, 0x65, 0x72, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x44, 0x0a, 0x1e, 0x67, 0x72, 0x69, 0x62, 0x69, 0x5f, 0x65, - 0x6e, 0x63, 0x61, 0x70, 0x5f, 0x68, 0x65, 0x61, 0x64, 0x65, 0x72, 0x5f, 0x75, 0x6e, 0x73, 0x75, - 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xf9, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1b, - 0x67, 0x72, 0x69, 0x62, 0x69, 0x45, 0x6e, 0x63, 0x61, 0x70, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, - 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, 0x1d, 0x70, - 0x34, 0x72, 0x74, 0x5f, 0x63, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xfa, 0x01, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x1b, 0x70, 0x34, 0x72, 0x74, 0x43, 0x61, 0x70, 0x61, 0x62, 0x69, 0x6c, - 0x69, 0x74, 0x69, 0x65, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x12, 0x3f, 0x0a, 0x1c, 0x67, 0x6e, 0x6d, 0x69, 0x5f, 0x67, 0x65, 0x74, 0x5f, 0x6f, 0x6e, 0x5f, - 0x72, 0x6f, 0x6f, 0x74, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0xfb, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x67, 0x6e, 0x6d, 0x69, 0x47, 0x65, 0x74, - 0x4f, 0x6e, 0x52, 0x6f, 0x6f, 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x61, 0x0a, 0x2d, 0x70, 0x61, 0x63, 0x6b, 0x65, 0x74, 0x5f, 0x70, 0x72, 0x6f, 0x63, - 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x5f, 0x61, 0x67, 0x67, 0x72, 0x65, 0x67, 0x61, 0x74, 0x65, - 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0xfc, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x29, 0x70, 0x61, 0x63, 0x6b, 0x65, - 0x74, 0x50, 0x72, 0x6f, 0x63, 0x65, 0x73, 0x73, 0x69, 0x6e, 0x67, 0x41, 0x67, 0x67, 0x72, 0x65, - 0x67, 0x61, 0x74, 0x65, 0x44, 0x72, 0x6f, 0x70, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x48, 0x0a, 0x20, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, - 0x5f, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x5f, 0x64, 0x72, 0x6f, 0x70, 0x73, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0xfd, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x1d, 0x66, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x54, 0x6f, 0x74, 0x61, 0x6c, 0x44, 0x72, - 0x6f, 0x70, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, - 0x0a, 0x1e, 0x62, 0x67, 0x70, 0x5f, 0x70, 0x72, 0x65, 0x66, 0x69, 0x78, 0x73, 0x65, 0x74, 0x5f, - 0x72, 0x65, 0x71, 0x5f, 0x72, 0x6f, 0x75, 0x74, 0x65, 0x70, 0x6f, 0x6c, 0x5f, 0x72, 0x65, 0x66, - 0x18, 0xff, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x62, 0x67, 0x70, 0x50, 0x72, 0x65, 0x66, - 0x69, 0x78, 0x73, 0x65, 0x74, 0x52, 0x65, 0x71, 0x52, 0x6f, 0x75, 0x74, 0x65, 0x70, 0x6f, 0x6c, - 0x52, 0x65, 0x66, 0x12, 0x43, 0x0a, 0x1e, 0x6f, 0x70, 0x65, 0x72, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x5f, 0x66, 0x6f, 0x72, 0x5f, 0x69, 0x63, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, - 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x80, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x6f, 0x70, - 0x65, 0x72, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x46, 0x6f, 0x72, 0x49, 0x63, 0x55, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x2f, 0x0a, 0x13, 0x65, 0x78, 0x70, 0x6c, - 0x69, 0x63, 0x69, 0x74, 0x5f, 0x64, 0x63, 0x6f, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, - 0x81, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x65, 0x78, 0x70, 0x6c, 0x69, 0x63, 0x69, 0x74, - 0x44, 0x63, 0x6f, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x29, 0x76, 0x65, 0x72, - 0x69, 0x66, 0x79, 0x5f, 0x65, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x5f, 0x62, 0x72, 0x65, - 0x61, 0x6b, 0x6f, 0x75, 0x74, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x82, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x76, - 0x65, 0x72, 0x69, 0x66, 0x79, 0x45, 0x78, 0x70, 0x65, 0x63, 0x74, 0x65, 0x64, 0x42, 0x72, 0x65, - 0x61, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x12, 0x3b, 0x0a, 0x19, 0x62, 0x67, 0x70, 0x5f, 0x61, 0x73, 0x70, 0x61, - 0x74, 0x68, 0x73, 0x65, 0x74, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x18, 0x83, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x62, 0x67, 0x70, 0x41, 0x73, 0x70, - 0x61, 0x74, 0x68, 0x73, 0x65, 0x74, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, - 0x64, 0x12, 0x3a, 0x0a, 0x19, 0x73, 0x72, 0x5f, 0x69, 0x67, 0x70, 0x5f, 0x63, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x84, - 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x16, 0x73, 0x72, 0x49, 0x67, 0x70, 0x43, 0x6f, 0x6e, 0x66, - 0x69, 0x67, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x6f, 0x0a, - 0x35, 0x73, 0x65, 0x74, 0x5f, 0x69, 0x73, 0x69, 0x73, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x5f, 0x77, - 0x69, 0x74, 0x68, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x61, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, - 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x85, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x2f, 0x73, - 0x65, 0x74, 0x49, 0x73, 0x69, 0x73, 0x41, 0x75, 0x74, 0x68, 0x57, 0x69, 0x74, 0x68, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x54, - 0x0a, 0x27, 0x67, 0x72, 0x65, 0x5f, 0x67, 0x75, 0x65, 0x5f, 0x74, 0x75, 0x6e, 0x6e, 0x65, 0x6c, - 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x5f, 0x6f, 0x63, 0x5f, 0x75, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x86, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x22, 0x67, 0x72, 0x65, 0x47, 0x75, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x49, 0x6e, - 0x74, 0x65, 0x72, 0x66, 0x61, 0x63, 0x65, 0x4f, 0x63, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x3e, 0x0a, 0x1b, 0x6c, 0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6e, 0x74, - 0x65, 0x72, 0x76, 0x61, 0x6c, 0x5f, 0x6e, 0x6f, 0x74, 0x5f, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, - 0x74, 0x65, 0x64, 0x18, 0x87, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x6c, 0x6f, 0x61, 0x64, - 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x53, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x12, 0x5a, 0x0a, 0x2a, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x6f, 0x70, 0x74, - 0x69, 0x63, 0x61, 0x6c, 0x5f, 0x63, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x5f, 0x6f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x5f, 0x70, 0x6f, 0x77, 0x65, 0x72, 0x5f, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, - 0x61, 0x6c, 0x18, 0x88, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x25, 0x73, 0x6b, 0x69, 0x70, 0x4f, - 0x70, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x43, 0x68, 0x61, 0x6e, 0x6e, 0x65, 0x6c, 0x4f, 0x75, 0x74, - 0x70, 0x75, 0x74, 0x50, 0x6f, 0x77, 0x65, 0x72, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, - 0x12, 0x41, 0x0a, 0x1c, 0x73, 0x6b, 0x69, 0x70, 0x5f, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x63, 0x65, - 0x69, 0x76, 0x65, 0x72, 0x5f, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, - 0x18, 0x89, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x73, 0x6b, 0x69, 0x70, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x63, 0x65, 0x69, 0x76, 0x65, 0x72, 0x44, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x3b, 0x0a, 0x19, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, - 0x7a, 0x5f, 0x6f, 0x63, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0x8a, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x17, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, - 0x65, 0x72, 0x7a, 0x4f, 0x63, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x12, 0x47, 0x0a, 0x20, 0x62, 0x67, 0x70, 0x5f, 0x64, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x5f, 0x6f, 0x63, 0x5f, 0x70, 0x61, 0x74, 0x68, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, - 0x72, 0x74, 0x65, 0x64, 0x18, 0x8b, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1c, 0x62, 0x67, 0x70, - 0x44, 0x69, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x4f, 0x63, 0x50, 0x61, 0x74, 0x68, 0x55, 0x6e, - 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x33, 0x0a, 0x15, 0x69, 0x73, 0x69, - 0x73, 0x5f, 0x6d, 0x70, 0x6c, 0x73, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x18, 0x8c, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x69, 0x73, 0x69, 0x73, 0x4d, - 0x70, 0x6c, 0x73, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x3d, - 0x0a, 0x1a, 0x61, 0x75, 0x74, 0x6f, 0x5f, 0x6e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, 0x74, 0x65, - 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x8d, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x18, 0x61, 0x75, 0x74, 0x6f, 0x4e, 0x65, 0x67, 0x6f, 0x74, 0x69, 0x61, - 0x74, 0x65, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x37, 0x0a, - 0x17, 0x64, 0x75, 0x70, 0x6c, 0x65, 0x78, 0x5f, 0x6d, 0x6f, 0x64, 0x65, 0x5f, 0x75, 0x6e, 0x73, - 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, 0x8e, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x15, 0x64, 0x75, 0x70, 0x6c, 0x65, 0x78, 0x4d, 0x6f, 0x64, 0x65, 0x55, 0x6e, 0x73, 0x75, 0x70, - 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x35, 0x0a, 0x16, 0x70, 0x6f, 0x72, 0x74, 0x5f, 0x73, - 0x70, 0x65, 0x65, 0x64, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, - 0x18, 0x8f, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x14, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x70, 0x65, - 0x65, 0x64, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x12, 0x43, 0x0a, - 0x1e, 0x62, 0x67, 0x70, 0x5f, 0x73, 0x65, 0x74, 0x5f, 0x6d, 0x65, 0x64, 0x5f, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x5f, 0x75, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x18, - 0x90, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x1a, 0x62, 0x67, 0x70, 0x53, 0x65, 0x74, 0x4d, 0x65, - 0x64, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x55, 0x6e, 0x73, 0x75, 0x70, 0x70, 0x6f, 0x72, 0x74, - 0x65, 0x64, 0x4a, 0x04, 0x08, 0x54, 0x10, 0x55, 0x4a, 0x04, 0x08, 0x09, 0x10, 0x0a, 0x4a, 0x04, - 0x08, 0x1c, 0x10, 0x1d, 0x4a, 0x04, 0x08, 0x14, 0x10, 0x15, 0x4a, 0x04, 0x08, 0x26, 0x10, 0x27, - 0x4a, 0x04, 0x08, 0x2b, 0x10, 0x2c, 0x4a, 0x04, 0x08, 0x5a, 0x10, 0x5b, 0x4a, 0x04, 0x08, 0x61, - 0x10, 0x62, 0x4a, 0x04, 0x08, 0x37, 0x10, 0x38, 0x4a, 0x04, 0x08, 0x59, 0x10, 0x5a, 0x4a, 0x04, - 0x08, 0x13, 0x10, 0x14, 0x4a, 0x04, 0x08, 0x24, 0x10, 0x25, 0x4a, 0x04, 0x08, 0x23, 0x10, 0x24, - 0x4a, 0x04, 0x08, 0x28, 0x10, 0x29, 0x4a, 0x04, 0x08, 0x71, 0x10, 0x72, 0x4a, 0x06, 0x08, 0x83, - 0x01, 0x10, 0x84, 0x01, 0x4a, 0x06, 0x08, 0x8d, 0x01, 0x10, 0x8e, 0x01, 0x4a, 0x06, 0x08, 0xad, - 0x01, 0x10, 0xae, 0x01, 0x4a, 0x06, 0x08, 0xea, 0x01, 0x10, 0xeb, 0x01, 0x4a, 0x06, 0x08, 0xfe, - 0x01, 0x10, 0xff, 0x01, 0x1a, 0xa0, 0x01, 0x0a, 0x12, 0x50, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, - 0x6d, 0x45, 0x78, 0x63, 0x65, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x08, 0x70, - 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x25, 0x2e, - 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x69, - 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x2e, 0x50, 0x6c, 0x61, 0x74, - 0x66, 0x6f, 0x72, 0x6d, 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x47, - 0x0a, 0x0a, 0x64, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x6f, 0x70, 0x65, 0x6e, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, - 0x74, 0x65, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x2e, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, - 0x2e, 0x44, 0x65, 0x76, 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x0a, 0x64, 0x65, 0x76, - 0x69, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xf8, 0x02, 0x0a, 0x07, 0x54, 0x65, 0x73, 0x74, - 0x62, 0x65, 0x64, 0x12, 0x17, 0x0a, 0x13, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x55, - 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, 0x12, 0x0f, 0x0a, 0x0b, - 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x10, 0x01, 0x12, 0x1a, 0x0a, - 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x44, 0x55, 0x54, - 0x5f, 0x34, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x02, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, - 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x32, 0x4c, 0x49, - 0x4e, 0x4b, 0x53, 0x10, 0x03, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, - 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x34, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, - 0x04, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, - 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x39, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x5f, 0x4c, 0x41, 0x47, 0x10, - 0x05, 0x12, 0x1e, 0x0a, 0x1a, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, - 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x32, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, - 0x06, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, - 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x38, 0x4c, 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x07, 0x12, 0x15, 0x0a, - 0x11, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x34, 0x30, 0x30, - 0x5a, 0x52, 0x10, 0x08, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, - 0x44, 0x55, 0x54, 0x5f, 0x34, 0x30, 0x30, 0x5a, 0x52, 0x5f, 0x50, 0x4c, 0x55, 0x53, 0x10, 0x09, - 0x12, 0x21, 0x0a, 0x1d, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, - 0x34, 0x30, 0x30, 0x5a, 0x52, 0x5f, 0x31, 0x30, 0x30, 0x47, 0x5f, 0x34, 0x4c, 0x49, 0x4e, 0x4b, - 0x53, 0x10, 0x0a, 0x12, 0x21, 0x0a, 0x1d, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, 0x44, 0x5f, 0x44, - 0x55, 0x54, 0x5f, 0x34, 0x30, 0x30, 0x46, 0x52, 0x5f, 0x31, 0x30, 0x30, 0x47, 0x5f, 0x34, 0x4c, - 0x49, 0x4e, 0x4b, 0x53, 0x10, 0x0b, 0x12, 0x1a, 0x0a, 0x16, 0x54, 0x45, 0x53, 0x54, 0x42, 0x45, - 0x44, 0x5f, 0x44, 0x55, 0x54, 0x5f, 0x41, 0x54, 0x45, 0x5f, 0x35, 0x4c, 0x49, 0x4e, 0x4b, 0x53, - 0x10, 0x0c, 0x22, 0x6d, 0x0a, 0x04, 0x54, 0x61, 0x67, 0x73, 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, - 0x47, 0x53, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x00, - 0x12, 0x14, 0x0a, 0x10, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x41, 0x47, 0x47, 0x52, 0x45, 0x47, 0x41, - 0x54, 0x49, 0x4f, 0x4e, 0x10, 0x01, 0x12, 0x18, 0x0a, 0x14, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x44, - 0x41, 0x54, 0x41, 0x43, 0x45, 0x4e, 0x54, 0x45, 0x52, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x10, 0x02, - 0x12, 0x0d, 0x0a, 0x09, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x45, 0x44, 0x47, 0x45, 0x10, 0x03, 0x12, - 0x10, 0x0a, 0x0c, 0x54, 0x41, 0x47, 0x53, 0x5f, 0x54, 0x52, 0x41, 0x4e, 0x53, 0x49, 0x54, 0x10, - 0x04, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, -} +const file_metadata_proto_rawDesc = "" + + "\n" + + "\x0emetadata.proto\x12\x12openconfig.testing\x1a1github.com/openconfig/ondatra/proto/testbed.proto\"ϰ\x01\n" + + "\bMetadata\x12\x12\n" + + "\x04uuid\x18\x01 \x01(\tR\x04uuid\x12\x17\n" + + "\aplan_id\x18\x02 \x01(\tR\x06planId\x12 \n" + + "\vdescription\x18\x03 \x01(\tR\vdescription\x12>\n" + + "\atestbed\x18\x04 \x01(\x0e2$.openconfig.testing.Metadata.TestbedR\atestbed\x12`\n" + + "\x13platform_exceptions\x18\x05 \x03(\v2/.openconfig.testing.Metadata.PlatformExceptionsR\x12platformExceptions\x125\n" + + "\x04tags\x18\x06 \x03(\x0e2!.openconfig.testing.Metadata.TagsR\x04tags\x12,\n" + + "\x12path_presence_test\x18\a \x01(\bR\x10pathPresenceTest\x1a\xb8\x01\n" + + "\bPlatform\x12.\n" + + "\x06vendor\x18\x01 \x01(\x0e2\x16.ondatra.Device.VendorR\x06vendor\x120\n" + + "\x14hardware_model_regex\x18\x03 \x01(\tR\x12hardwareModelRegex\x124\n" + + "\x16software_version_regex\x18\x04 \x01(\tR\x14softwareVersionRegexJ\x04\b\x02\x10\x03R\x0ehardware_model\x1a\xf0\xa6\x01\n" + + "\n" + + "Deviations\x120\n" + + "\x14ipv4_missing_enabled\x18\x01 \x01(\bR\x12ipv4MissingEnabled\x129\n" + + "\x18traceroute_fragmentation\x18\x02 \x01(\bR\x17tracerouteFragmentation\x12;\n" + + "\x1atraceroute_l4_protocol_udp\x18\x03 \x01(\bR\x17tracerouteL4ProtocolUdp\x12:\n" + + "\x19prepolicy_received_routes\x18\x04 \x01(\bR\x17prepolicyReceivedRoutes\x12W\n" + + "(hierarchical_weight_resolution_tolerance\x18\x05 \x01(\x01R%hierarchicalWeightResolutionTolerance\x12E\n" + + "\x1fisis_multi_topology_unsupported\x18\x06 \x01(\bR\x1cisisMultiTopologyUnsupported\x12R\n" + + "&isis_interface_level1_disable_required\x18\a \x01(\bR\"isisInterfaceLevel1DisableRequired\x12A\n" + + "\x1disis_single_topology_required\x18\b \x01(\bR\x1aisisSingleTopologyRequired\x12C\n" + + "\x1eisis_instance_enabled_required\x18\n" + + " \x01(\bR\x1bisisInstanceEnabledRequired\x12Q\n" + + "&missing_isis_interface_afi_safi_enable\x18\v \x01(\bR!missingIsisInterfaceAfiSafiEnable\x12T\n" + + "'isis_global_authentication_not_required\x18\f \x01(\bR#isisGlobalAuthenticationNotRequired\x12X\n" + + ")isis_explicit_level_authentication_config\x18\r \x01(\bR%isisExplicitLevelAuthenticationConfig\x12I\n" + + "!isis_restart_suppress_unsupported\x18\x0e \x01(\bR\x1eisisRestartSuppressUnsupported\x12.\n" + + "\x13ip_neighbor_missing\x18\x0f \x01(\bR\x11ipNeighborMissing\x12/\n" + + "\x13osactivate_noreboot\x18\x10 \x01(\bR\x12osactivateNoreboot\x127\n" + + "\x18osinstall_for_standby_rp\x18\x11 \x01(\bR\x15osinstallForStandbyRp\x12P\n" + + "%lldp_interface_config_override_global\x18\x12 \x01(\bR!lldpInterfaceConfigOverrideGlobal\x12U\n" + + "(missing_bgp_last_notification_error_code\x18\x15 \x01(\bR#missingBgpLastNotificationErrorCode\x12G\n" + + " interface_ref_config_unsupported\x18\x16 \x01(\bR\x1dinterfaceRefConfigUnsupported\x124\n" + + "\x16state_path_unsupported\x18\x17 \x01(\bR\x14statePathUnsupported\x12?\n" + + "\x1dipv6_enable_for_gribi_nh_dmac\x18\x18 \x01(\bR\x18ipv6EnableForGribiNhDmac\x12E\n" + + "\x1fecn_profile_required_definition\x18\x19 \x01(\bR\x1cecnProfileRequiredDefinition\x12E\n" + + "\x1fipv6_discarded_pkts_unsupported\x18\x1a \x01(\bR\x1cipv6DiscardedPktsUnsupported\x12C\n" + + "\x1edrop_weight_leaves_unsupported\x18\x1b \x01(\bR\x1bdropWeightLeavesUnsupported\x12>\n" + + "\x1ccli_takes_precedence_over_oc\x18\x1d \x01(\bR\x18cliTakesPrecedenceOverOc\x12?\n" + + "\x1cscheduler_input_weight_limit\x18\x1e \x01(\bR\x19schedulerInputWeightLimit\x12;\n" + + "\x1aswitch_chip_id_unsupported\x18\x1f \x01(\bR\x17switchChipIdUnsupported\x12Q\n" + + "%backplane_facing_capacity_unsupported\x18 \x01(\bR\"backplaneFacingCapacityUnsupported\x12I\n" + + "!interface_counters_from_container\x18! \x01(\bR\x1einterfaceCountersFromContainer\x12Z\n" + + "+no_mix_of_tagged_and_untagged_subinterfaces\x18\" \x01(\bR%noMixOfTaggedAndUntaggedSubinterfaces\x124\n" + + "\x16sw_version_unsupported\x18% \x01(\bR\x14swVersionUnsupported\x12B\n" + + "\x1dstorage_component_unsupported\x18' \x01(\bR\x1bstorageComponentUnsupported\x12.\n" + + "\x13explicit_port_speed\x18) \x01(\bR\x11explicitPortSpeed\x12H\n" + + "!explicit_interface_in_default_vrf\x18* \x01(\bR\x1dexplicitInterfaceInDefaultVrf\x12O\n" + + "$subinterface_packet_counters_missing\x18, \x01(\bR!subinterfacePacketCountersMissing\x12#\n" + + "\rconnect_retry\x18- \x01(\bR\fconnectRetry\x12I\n" + + "\"gribi_mac_override_with_static_arp\x18. \x01(\bR\x1dgribiMacOverrideWithStaticArp\x12J\n" + + "\"route_policy_under_afi_unsupported\x18/ \x01(\bR\x1eroutePolicyUnderAfiUnsupported\x12V\n" + + "(gnoi_fabric_component_reboot_unsupported\x180 \x01(\bR$gnoiFabricComponentRebootUnsupported\x12D\n" + + "\x1fntp_non_default_vrf_unsupported\x181 \x01(\bR\x1bntpNonDefaultVrfUnsupported\x12\x1e\n" + + "\vomit_l2_mtu\x182 \x01(\bR\tomitL2Mtu\x12F\n" + + " skip_controller_card_power_admin\x183 \x01(\bR\x1cskipControllerCardPowerAdmin\x12)\n" + + "\x10banner_delimiter\x18< \x01(\tR\x0fbannerDelimiter\x12.\n" + + "\x13bgp_tolerance_value\x18= \x01(\x05R\x11bgpToleranceValue\x12M\n" + + "$link_qual_wait_after_delete_required\x18> \x01(\bR\x1flinkQualWaitAfterDeleteRequired\x12C\n" + + "\x1egnoi_status_empty_subcomponent\x18? \x01(\bR\x1bgnoiStatusEmptySubcomponent\x12V\n" + + "(network_instance_table_deletion_required\x18@ \x01(\bR$networkInstanceTableDeletionRequired\x123\n" + + "\x16bgp_md5_requires_reset\x18A \x01(\bR\x13bgpMd5RequiresReset\x12K\n" + + "#dequeue_delete_not_counted_as_drops\x18B \x01(\bR\x1edequeueDeleteNotCountedAsDrops\x12*\n" + + "\x11gribi_riback_only\x18C \x01(\bR\x0fgribiRibackOnly\x126\n" + + "\x17aggregate_atomic_update\x18D \x01(\bR\x15aggregateAtomicUpdate\x12;\n" + + "\x1amissing_value_for_defaults\x18E \x01(\bR\x17missingValueForDefaults\x120\n" + + "\x14static_protocol_name\x18F \x01(\tR\x12staticProtocolName\x124\n" + + "\x16gnoi_subcomponent_path\x18G \x01(\bR\x14gnoiSubcomponentPath\x12L\n" + + "#interface_config_vrf_before_address\x18H \x01(\bR\x1finterfaceConfigVrfBeforeAddress\x12,\n" + + "\x12deprecated_vlan_id\x18I \x01(\bR\x10deprecatedVlanId\x12X\n" + + "*gribi_mac_override_static_arp_static_route\x18J \x01(\bR$gribiMacOverrideStaticArpStaticRoute\x12+\n" + + "\x11interface_enabled\x18K \x01(\bR\x10interfaceEnabled\x12\x1d\n" + + "\n" + + "qos_octets\x18L \x01(\bR\tqosOctets\x120\n" + + "\x14cpu_missing_ancestor\x18M \x01(\bR\x12cpuMissingAncestor\x12A\n" + + "\x1drequire_routed_subinterface_0\x18N \x01(\bR\x1arequireRoutedSubinterface0\x12_\n" + + "-gnoi_switchover_reason_missing_user_initiated\x18O \x01(\bR(gnoiSwitchoverReasonMissingUserInitiated\x128\n" + + "\x18default_network_instance\x18P \x01(\tR\x16defaultNetworkInstance\x12O\n" + + "$p4rt_unsetelectionid_primary_allowed\x18Q \x01(\bR!p4rtUnsetelectionidPrimaryAllowed\x12;\n" + + "\x1abkup_arbitration_resp_code\x18R \x01(\bR\x17bkupArbitrationRespCode\x12I\n" + + "\"backup_nhg_requires_vrf_with_decap\x18S \x01(\bR\x1dbackupNhgRequiresVrfWithDecap\x12C\n" + + "\x1eisis_interface_afi_unsupported\x18U \x01(\bR\x1bisisInterfaceAfiUnsupported\x12L\n" + + "#p4rt_modify_table_entry_unsupported\x18V \x01(\bR\x1fp4rtModifyTableEntryUnsupported\x12^\n" + + "-os_component_parent_is_supervisor_or_linecard\x18W \x01(\bR'osComponentParentIsSupervisorOrLinecard\x12B\n" + + "\x1eos_component_parent_is_chassis\x18X \x01(\bR\x1aosComponentParentIsChassis\x12W\n" + + "*isis_require_same_l1_metric_with_l2_metric\x18[ \x01(\bR#isisRequireSameL1MetricWithL2Metric\x12W\n" + + "*bgp_set_med_requires_equal_ospf_set_metric\x18\\ \x01(\bR#bgpSetMedRequiresEqualOspfSetMetric\x12N\n" + + "$p4rt_gdp_requires_dot1q_subinterface\x18] \x01(\bR p4rtGdpRequiresDot1qSubinterface\x12Y\n" + + "*ate_port_link_state_operations_unsupported\x18^ \x01(\bR%atePortLinkStateOperationsUnsupported\x12&\n" + + "\x0fset_native_user\x18_ \x01(\bR\rsetNativeUser\x12O\n" + + "$linecard_cpu_utilization_unsupported\x18b \x01(\bR!linecardCpuUtilizationUnsupported\x12S\n" + + "&consistent_component_names_unsupported\x18c \x01(\bR#consistentComponentNamesUnsupported\x12\\\n" + + "+controller_card_cpu_utilization_unsupported\x18d \x01(\bR'controllerCardCpuUtilizationUnsupported\x12E\n" + + "\x1ffabric_drop_counter_unsupported\x18e \x01(\bR\x1cfabricDropCounterUnsupported\x12U\n" + + "'linecard_memory_utilization_unsupported\x18f \x01(\bR$linecardMemoryUtilizationUnsupported\x12F\n" + + " qos_voq_drop_counter_unsupported\x18g \x01(\bR\x1cqosVoqDropCounterUnsupported\x12D\n" + + "\x1fate_ipv6_flow_label_unsupported\x18h \x01(\bR\x1bateIpv6FlowLabelUnsupported\x12P\n" + + "%isis_timers_csnp_interval_unsupported\x18i \x01(\bR!isisTimersCsnpIntervalUnsupported\x12q\n" + + "7isis_counter_manual_address_drop_from_areas_unsupported\x18j \x01(\bR0isisCounterManualAddressDropFromAreasUnsupported\x12P\n" + + "%isis_counter_part_changes_unsupported\x18k \x01(\bR!isisCounterPartChangesUnsupported\x12L\n" + + "\"transceiver_thresholds_unsupported\x18l \x01(\bR transceiverThresholdsUnsupported\x12F\n" + + " interface_loopback_mode_raw_gnmi\x18m \x01(\bR\x1cinterfaceLoopbackModeRawGnmi\x12@\n" + + "\x1dskip_tcp_negotiated_mss_check\x18n \x01(\bR\x19skipTcpNegotiatedMssCheck\x12L\n" + + "#isis_lsp_metadata_leafs_unsupported\x18o \x01(\bR\x1fisisLspMetadataLeafsUnsupported\x121\n" + + "\x15qos_queue_requires_id\x18p \x01(\bR\x12qosQueueRequiresId\x12P\n" + + "%qos_buffer_allocation_config_required\x18r \x01(\bR!qosBufferAllocationConfigRequired\x12f\n" + + "1bgp_global_extended_next_hop_encoding_unsupported\x18s \x01(\bR+bgpGlobalExtendedNextHopEncodingUnsupported\x121\n" + + "\x15bgp_llgr_oc_undefined\x18t \x01(\bR\x12bgpLlgrOcUndefined\x12A\n" + + "\x1dtunnel_state_path_unsupported\x18u \x01(\bR\x1atunnelStatePathUnsupported\x12C\n" + + "\x1etunnel_config_path_unsupported\x18v \x01(\bR\x1btunnelConfigPathUnsupported\x12Q\n" + + "&ecn_same_min_max_threshold_unsupported\x18w \x01(\bR!ecnSameMinMaxThresholdUnsupported\x12A\n" + + "\x1dqos_scheduler_config_required\x18x \x01(\bR\x1aqosSchedulerConfigRequired\x12H\n" + + "!qos_set_weight_config_unsupported\x18y \x01(\bR\x1dqosSetWeightConfigUnsupported\x12B\n" + + "\x1eqos_get_state_path_unsupported\x18z \x01(\bR\x1aqosGetStatePathUnsupported\x12,\n" + + "\x12isis_level_enabled\x18{ \x01(\bR\x10isisLevelEnabled\x12H\n" + + "!interface_ref_interface_id_format\x18| \x01(\bR\x1dinterfaceRefInterfaceIdFormat\x12G\n" + + " member_link_loopback_unsupported\x18} \x01(\bR\x1dmemberLinkLoopbackUnsupported\x12M\n" + + "$skip_plq_interface_oper_status_check\x18~ \x01(\bR\x1fskipPlqInterfaceOperStatusCheck\x12J\n" + + "\"bgp_explicit_prefix_limit_received\x18\x7f \x01(\bR\x1ebgpExplicitPrefixLimitReceived\x12X\n" + + ")bgp_missing_oc_max_prefixes_configuration\x18\x80\x01 \x01(\bR$bgpMissingOcMaxPrefixesConfiguration\x12R\n" + + "&skip_bgp_session_check_without_afisafi\x18\x81\x01 \x01(\bR!skipBgpSessionCheckWithoutAfisafi\x12b\n" + + ".mismatched_hardware_resource_name_in_component\x18\x82\x01 \x01(\bR)mismatchedHardwareResourceNameInComponent\x12]\n" + + "+gnoi_subcomponent_reboot_status_unsupported\x18\x84\x01 \x01(\bR'gnoiSubcomponentRebootStatusUnsupported\x12D\n" + + "\x1fskip_non_bgp_route_export_check\x18\x85\x01 \x01(\bR\x1askipNonBgpRouteExportCheck\x12U\n" + + "'isis_metric_style_telemetry_unsupported\x18\x86\x01 \x01(\bR#isisMetricStyleTelemetryUnsupported\x12c\n" + + "/static_route_next_hop_interface_ref_unsupported\x18\x87\x01 \x01(\bR)staticRouteNextHopInterfaceRefUnsupported\x12:\n" + + "\x19skip_static_nexthop_check\x18\x88\x01 \x01(\bR\x16skipStaticNexthopCheck\x12_\n" + + ",ipv6_router_advertisement_config_unsupported\x18\x8a\x01 \x01(\bR(ipv6RouterAdvertisementConfigUnsupported\x12]\n" + + "+prefix_limit_exceeded_telemetry_unsupported\x18\x8b\x01 \x01(\bR'prefixLimitExceededTelemetryUnsupported\x12C\n" + + "\x1eskip_setting_allow_multiple_as\x18\x8c\x01 \x01(\bR\x1askipSettingAllowMultipleAs\x12K\n" + + "\"gribi_decap_mixed_plen_unsupported\x18\x8f\x01 \x01(\bR\x1egribiDecapMixedPlenUnsupported\x12.\n" + + "\x13skip_isis_set_level\x18\x90\x01 \x01(\bR\x10skipIsisSetLevel\x12D\n" + + "\x1fskip_isis_set_metric_style_type\x18\x91\x01 \x01(\bR\x1askipIsisSetMetricStyleType\x12@\n" + + "\x1dskip_set_rp_match_set_options\x18\x92\x01 \x01(\bR\x18skipSetRpMatchSetOptions\x12U\n" + + "'skip_setting_disable_metric_propagation\x18\x93\x01 \x01(\bR#skipSettingDisableMetricPropagation\x12b\n" + + ".bgp_conditions_match_community_set_unsupported\x18\x94\x01 \x01(\bR)bgpConditionsMatchCommunitySetUnsupported\x12A\n" + + "\x1dpf_require_match_default_rule\x18\x95\x01 \x01(\bR\x19pfRequireMatchDefaultRule\x12g\n" + + "1missing_port_to_optical_channel_component_mapping\x18\x96\x01 \x01(\bR+missingPortToOpticalChannelComponentMapping\x12+\n" + + "\x11skip_container_op\x18\x97\x01 \x01(\bR\x0fskipContainerOp\x12Q\n" + + "%reorder_calls_for_vendor_compatibilty\x18\x98\x01 \x01(\bR!reorderCallsForVendorCompatibilty\x12D\n" + + "\x1fadd_missing_base_config_via_cli\x18\x99\x01 \x01(\bR\x1aaddMissingBaseConfigViaCli\x123\n" + + "\x15skip_macaddress_check\x18\x9a\x01 \x01(\bR\x13skipMacaddressCheck\x12=\n" + + "\x1bbgp_rib_oc_path_unsupported\x18\x9b\x01 \x01(\bR\x17bgpRibOcPathUnsupported\x120\n" + + "\x14skip_prefix_set_mode\x18\x9c\x01 \x01(\bR\x11skipPrefixSetMode\x128\n" + + "\x18set_metric_as_preference\x18\x9d\x01 \x01(\bR\x15setMetricAsPreference\x12r\n" + + "8ipv6_static_route_with_ipv4_next_hop_requires_static_arp\x18\x9e\x01 \x01(\bR/ipv6StaticRouteWithIpv4NextHopRequiresStaticArp\x12P\n" + + "%pf_require_sequential_order_pbr_rules\x18\x9f\x01 \x01(\bR pfRequireSequentialOrderPbrRules\x12a\n" + + ".missing_static_route_next_hop_metric_telemetry\x18\xa0\x01 \x01(\bR(missingStaticRouteNextHopMetricTelemetry\x12X\n" + + ")unsupported_static_route_next_hop_recurse\x18\xa1\x01 \x01(\bR$unsupportedStaticRouteNextHopRecurse\x12]\n" + + ",missing_static_route_drop_next_hop_telemetry\x18\xa2\x01 \x01(\bR&missingStaticRouteDropNextHopTelemetry\x12s\n" + + "7missing_zr_optical_channel_tunable_parameters_telemetry\x18\xa3\x01 \x01(\bR1missingZrOpticalChannelTunableParametersTelemetry\x12F\n" + + "\x1fplq_reflector_stats_unsupported\x18\xa4\x01 \x01(\bR\x1cplqReflectorStatsUnsupported\x12K\n" + + "\"plq_generator_capabilities_max_mtu\x18\xa5\x01 \x01(\rR\x1eplqGeneratorCapabilitiesMaxMtu\x12K\n" + + "\"plq_generator_capabilities_max_pps\x18\xa6\x01 \x01(\x04R\x1eplqGeneratorCapabilitiesMaxPps\x12W\n" + + "(bgp_extended_community_index_unsupported\x18\xa7\x01 \x01(\bR$bgpExtendedCommunityIndexUnsupported\x12K\n" + + "\"bgp_community_set_refs_unsupported\x18\xa8\x01 \x01(\bR\x1ebgpCommunitySetRefsUnsupported\x12\x1c\n" + + "\trib_wecmp\x18\xa9\x01 \x01(\bR\bribWecmp\x12C\n" + + "\x1dtable_connections_unsupported\x18\xaa\x01 \x01(\bR\x1btableConnectionsUnsupported\x12F\n" + + " use_vendor_native_tag_set_config\x18\xab\x01 \x01(\bR\x1buseVendorNativeTagSetConfig\x12?\n" + + "\x1cskip_bgp_send_community_type\x18\xac\x01 \x01(\bR\x18skipBgpSendCommunityType\x12^\n" + + ",bgp_actions_set_community_method_unsupported\x18\xae\x01 \x01(\bR'bgpActionsSetCommunityMethodUnsupported\x12*\n" + + "\x11set_no_peer_group\x18\xaf\x01 \x01(\bR\x0esetNoPeerGroup\x12F\n" + + " bgp_community_member_is_a_string\x18\xb0\x01 \x01(\bR\x1bbgpCommunityMemberIsAString\x12Y\n" + + "*ipv4_static_route_with_ipv6_nh_unsupported\x18\xb1\x01 \x01(\bR$ipv4StaticRouteWithIpv6NhUnsupported\x12Y\n" + + "*ipv6_static_route_with_ipv4_nh_unsupported\x18\xb2\x01 \x01(\bR$ipv6StaticRouteWithIpv4NhUnsupported\x129\n" + + "\x19static_route_with_drop_nh\x18\xb3\x01 \x01(\bR\x15staticRouteWithDropNh\x12I\n" + + "!static_route_with_explicit_metric\x18\xb4\x01 \x01(\bR\x1dstaticRouteWithExplicitMetric\x12D\n" + + "\x1ebgp_default_policy_unsupported\x18\xb5\x01 \x01(\bR\x1bbgpDefaultPolicyUnsupported\x12J\n" + + "\"explicit_enable_bgp_on_default_vrf\x18\xb6\x01 \x01(\bR\x1dexplicitEnableBgpOnDefaultVrf\x12E\n" + + "\x1frouting_policy_tag_set_embedded\x18\xb7\x01 \x01(\bR\x1broutingPolicyTagSetEmbedded\x12P\n" + + "&skip_afi_safi_path_for_bgp_multiple_as\x18\xb8\x01 \x01(\bR\x1fskipAfiSafiPathForBgpMultipleAs\x12L\n" + + "\"community_member_regex_unsupported\x18\xb9\x01 \x01(\bR\x1fcommunityMemberRegexUnsupported\x12F\n" + + " same_policy_attached_to_all_afis\x18\xba\x01 \x01(\bR\x1bsamePolicyAttachedToAllAfis\x12I\n" + + "!skip_setting_statement_for_policy\x18\xbb\x01 \x01(\bR\x1dskipSettingStatementForPolicy\x12B\n" + + "\x1dskip_checking_attribute_index\x18\xbc\x01 \x01(\bR\x1askipCheckingAttributeIndex\x12U\n" + + "'flatten_policy_with_multiple_statements\x18\xbd\x01 \x01(\bR#flattenPolicyWithMultipleStatements\x12H\n" + + " default_route_policy_unsupported\x18\xbe\x01 \x01(\bR\x1ddefaultRoutePolicyUnsupported\x125\n" + + "\x16slaac_prefix_length128\x18\xbf\x01 \x01(\bR\x14slaacPrefixLength128\x12M\n" + + "#bgp_max_multipath_paths_unsupported\x18\xc0\x01 \x01(\bR\x1fbgpMaxMultipathPathsUnsupported\x12Y\n" + + ")multipath_unsupported_neighbor_or_afisafi\x18\xc1\x01 \x01(\bR%multipathUnsupportedNeighborOrAfisafi\x125\n" + + "\x16model_name_unsupported\x18\xc2\x01 \x01(\bR\x14modelNameUnsupported\x12e\n" + + "/community_match_with_redistribution_unsupported\x18\xc3\x01 \x01(\bR+communityMatchWithRedistributionUnsupported\x12j\n" + + "2install_position_and_install_component_unsupported\x18\xc4\x01 \x01(\bR-installPositionAndInstallComponentUnsupported\x12W\n" + + ")encap_tunnel_shut_backup_nhg_zero_traffic\x18\xc5\x01 \x01(\bR#encapTunnelShutBackupNhgZeroTraffic\x12%\n" + + "\x0emax_ecmp_paths\x18\xc6\x01 \x01(\bR\fmaxEcmpPaths\x125\n" + + "\x16wecmp_auto_unsupported\x18\xc7\x01 \x01(\bR\x14wecmpAutoUnsupported\x12N\n" + + "#routing_policy_chaining_unsupported\x18\xc8\x01 \x01(\bR routingPolicyChainingUnsupported\x125\n" + + "\x16isis_loopback_required\x18\xc9\x01 \x01(\bR\x14isisLoopbackRequired\x12U\n" + + "'weighted_ecmp_fixed_packet_verification\x18\xca\x01 \x01(\bR#weightedEcmpFixedPacketVerification\x12:\n" + + "\x19override_default_nh_scale\x18\xcb\x01 \x01(\bR\x16overrideDefaultNhScale\x12S\n" + + "&bgp_extended_community_set_unsupported\x18\xcc\x01 \x01(\bR\"bgpExtendedCommunitySetUnsupported\x12Y\n" + + "*bgp_set_ext_community_set_refs_unsupported\x18\xcd\x01 \x01(\bR$bgpSetExtCommunitySetRefsUnsupported\x12Q\n" + + "%bgp_delete_link_bandwidth_unsupported\x18\xce\x01 \x01(\bR!bgpDeleteLinkBandwidthUnsupported\x12O\n" + + "$qos_inqueue_drop_counter_unsupported\x18\xcf\x01 \x01(\bR qosInqueueDropCounterUnsupported\x12S\n" + + "&bgp_explicit_extended_community_enable\x18\xd0\x01 \x01(\bR\"bgpExplicitExtendedCommunityEnable\x12M\n" + + "#match_tag_set_condition_unsupported\x18\xd1\x01 \x01(\bR\x1fmatchTagSetConditionUnsupported\x12L\n" + + "#peer_group_def_ebgp_vrf_unsupported\x18\xd2\x01 \x01(\bR\x1epeerGroupDefEbgpVrfUnsupported\x12Z\n" + + "*redis_connected_under_ebgp_vrf_unsupported\x18\xd3\x01 \x01(\bR%redisConnectedUnderEbgpVrfUnsupported\x12W\n" + + "*bgp_afi_safi_in_default_ni_before_other_ni\x18\xd4\x01 \x01(\bR\"bgpAfiSafiInDefaultNiBeforeOtherNi\x12W\n" + + "(default_import_export_policy_unsupported\x18\xd5\x01 \x01(\bR$defaultImportExportPolicyUnsupported\x12c\n" + + ".ipv6_router_advertisement_interval_unsupported\x18\xd6\x01 \x01(\bR*ipv6RouterAdvertisementIntervalUnsupported\x12N\n" + + "$decap_nh_with_nexthop_ni_unsupported\x18\xd7\x01 \x01(\bR\x1fdecapNhWithNexthopNiUnsupported\x12H\n" + + " community_invert_any_unsupported\x18\xd8\x01 \x01(\bR\x1dcommunityInvertAnyUnsupported\x12U\n" + + "'sflow_source_address_update_unsupported\x18\xd9\x01 \x01(\bR#sflowSourceAddressUpdateUnsupported\x12.\n" + + "\x13link_local_mask_len\x18\xda\x01 \x01(\bR\x10linkLocalMaskLen\x12b\n" + + ".use_parent_component_for_temperature_telemetry\x18\xdb\x01 \x01(\bR)useParentComponentForTemperatureTelemetry\x12D\n" + + "\x1ecomponent_mfg_date_unsupported\x18\xdc\x01 \x01(\bR\x1bcomponentMfgDateUnsupported\x12@\n" + + "\x1cotn_channel_trib_unsupported\x18\xdd\x01 \x01(\bR\x19otnChannelTribUnsupported\x12[\n" + + "*eth_channel_ingress_parameters_unsupported\x18\xde\x01 \x01(\bRðChannelIngressParametersUnsupported\x12S\n" + + "ð_channel_assignment_cisco_numbering\x18\xdf\x01 \x01(\bR\"ethChannelAssignmentCiscoNumbering\x12J\n" + + "!interface_counters_update_delayed\x18\xe0\x01 \x01(\bR\x1einterfaceCountersUpdateDelayed\x12>\n" + + "\x1bchassis_get_rpc_unsupported\x18\xe1\x01 \x01(\bR\x18chassisGetRpcUnsupported\x12V\n" + + "(power_disable_enable_leaf_ref_validation\x18\xe2\x01 \x01(\bR#powerDisableEnableLeafRefValidation\x12F\n" + + "\x1fssh_server_counters_unsupported\x18\xe3\x01 \x01(\bR\x1csshServerCountersUnsupported\x12A\n" + + "\x1coperational_mode_unsupported\x18\xe4\x01 \x01(\bR\x1aoperationalModeUnsupported\x12Q\n" + + "&bgp_session_state_idle_in_passive_mode\x18\xe5\x01 \x01(\bR bgpSessionStateIdleInPassiveMode\x12E\n" + + "\x1fenable_multipath_under_afi_safi\x18\xe6\x01 \x01(\bR\x1benableMultipathUnderAfiSafi\x12S\n" + + "&otn_channel_assignment_cisco_numbering\x18\xe8\x01 \x01(\bR\"otnChannelAssignmentCiscoNumbering\x12F\n" + + " cisco_pre_fec_ber_inactive_value\x18\xe9\x01 \x01(\bR\x1bciscoPreFecBerInactiveValue\x12L\n" + + "#bgp_afi_safi_wildcard_not_supported\x18\xeb\x01 \x01(\bR\x1ebgpAfiSafiWildcardNotSupported\x129\n" + + "\x18enable_table_connections\x18\xec\x01 \x01(\bR\x16enableTableConnections\x12/\n" + + "\x13no_zero_suppression\x18\xed\x01 \x01(\bR\x11noZeroSuppression\x12W\n" + + "(isis_interface_level_passive_unsupported\x18\xee\x01 \x01(\bR$isisInterfaceLevelPassiveUnsupported\x12<\n" + + "\x1aisis_dis_sysid_unsupported\x18\xef\x01 \x01(\bR\x17isisDisSysidUnsupported\x12N\n" + + "#isis_database_overloads_unsupported\x18\xf0\x01 \x01(\bR isisDatabaseOverloadsUnsupported\x12;\n" + + "\x1abgp_set_med_v7_unsupported\x18\xf1\x01 \x01(\bR\x16bgpSetMedV7Unsupported\x12O\n" + + "$tc_default_import_policy_unsupported\x18\xf2\x01 \x01(\bR tcDefaultImportPolicyUnsupported\x12J\n" + + "!tc_metric_propagation_unsupported\x18\xf3\x01 \x01(\bR\x1etcMetricPropagationUnsupported\x12P\n" + + "$tc_attribute_propagation_unsupported\x18\xf4\x01 \x01(\bR!tcAttributePropagationUnsupported\x12?\n" + + "\x1btc_subscription_unsupported\x18\xf5\x01 \x01(\bR\x19tcSubscriptionUnsupported\x12:\n" + + "\x19default_bgp_instance_name\x18\xf6\x01 \x01(\tR\x16defaultBgpInstanceName\x12n\n" + + "4channel_assignment_rate_class_parameters_unsupported\x18\xf7\x01 \x01(\bR/channelAssignmentRateClassParametersUnsupported\x12Y\n" + + ")qos_scheduler_ingress_policer_unsupported\x18\xf8\x01 \x01(\bR%qosSchedulerIngressPolicerUnsupported\x12D\n" + + "\x1egribi_encap_header_unsupported\x18\xf9\x01 \x01(\bR\x1bgribiEncapHeaderUnsupported\x12C\n" + + "\x1dp4rt_capabilities_unsupported\x18\xfa\x01 \x01(\bR\x1bp4rtCapabilitiesUnsupported\x12?\n" + + "\x1cgnmi_get_on_root_unsupported\x18\xfb\x01 \x01(\bR\x18gnmiGetOnRootUnsupported\x12a\n" + + "-packet_processing_aggregate_drops_unsupported\x18\xfc\x01 \x01(\bR)packetProcessingAggregateDropsUnsupported\x12H\n" + + " fragment_total_drops_unsupported\x18\xfd\x01 \x01(\bR\x1dfragmentTotalDropsUnsupported\x12C\n" + + "\x1ebgp_prefixset_req_routepol_ref\x18\xff\x01 \x01(\bR\x1abgpPrefixsetReqRoutepolRef\x12C\n" + + "\x1eoper_status_for_ic_unsupported\x18\x80\x02 \x01(\bR\x1aoperStatusForIcUnsupported\x12/\n" + + "\x13explicit_dco_config\x18\x81\x02 \x01(\bR\x11explicitDcoConfig\x12Y\n" + + ")verify_expected_breakout_supported_config\x18\x82\x02 \x01(\bR%verifyExpectedBreakoutSupportedConfig\x12;\n" + + "\x19bgp_aspathset_unsupported\x18\x83\x02 \x01(\bR\x17bgpAspathsetUnsupported\x12:\n" + + "\x19sr_igp_config_unsupported\x18\x84\x02 \x01(\bR\x16srIgpConfigUnsupported\x12o\n" + + "5set_isis_auth_with_interface_authentication_container\x18\x85\x02 \x01(\bR/setIsisAuthWithInterfaceAuthenticationContainer\x12T\n" + + "'gre_gue_tunnel_interface_oc_unsupported\x18\x86\x02 \x01(\bR\"greGueTunnelInterfaceOcUnsupported\x12>\n" + + "\x1bload_interval_not_supported\x18\x87\x02 \x01(\bR\x18loadIntervalNotSupported\x12Z\n" + + "*skip_optical_channel_output_power_interval\x18\x88\x02 \x01(\bR%skipOpticalChannelOutputPowerInterval\x12A\n" + + "\x1cskip_transceiver_description\x18\x89\x02 \x01(\bR\x1askipTransceiverDescription\x12;\n" + + "\x19containerz_oc_unsupported\x18\x8a\x02 \x01(\bR\x17containerzOcUnsupported\x12G\n" + + " bgp_distance_oc_path_unsupported\x18\x8b\x02 \x01(\bR\x1cbgpDistanceOcPathUnsupported\x123\n" + + "\x15isis_mpls_unsupported\x18\x8c\x02 \x01(\bR\x13isisMplsUnsupported\x12=\n" + + "\x1aauto_negotiate_unsupported\x18\x8d\x02 \x01(\bR\x18autoNegotiateUnsupported\x127\n" + + "\x17duplex_mode_unsupported\x18\x8e\x02 \x01(\bR\x15duplexModeUnsupported\x125\n" + + "\x16port_speed_unsupported\x18\x8f\x02 \x01(\bR\x14portSpeedUnsupported\x12C\n" + + "\x1ebgp_set_med_action_unsupported\x18\x90\x02 \x01(\bR\x1abgpSetMedActionUnsupported\x12I\n" + + "!next_hop_group_config_unsupported\x18\x91\x02 \x01(\bR\x1dnextHopGroupConfigUnsupported\x12B\n" + + "\x1dqos_shaper_config_unsupported\x18\x92\x02 \x01(\bR\x1aqosShaperConfigUnsupported\x12@\n" + + "\x1cqos_shaper_state_unsupported\x18\x93\x02 \x01(\bR\x19qosShaperStateUnsupported\x12L\n" + + "\"ethernet_over_mplsogre_unsupported\x18\x94\x02 \x01(\bR\x1fethernetOverMplsogreUnsupported\x12,\n" + + "\x11sflow_unsupported\x18\x95\x02 \x01(\bR\x10sflowUnsupported\x12*\n" + + "\x10mpls_unsupported\x18\x96\x02 \x01(\bR\x0fmplsUnsupported\x12.\n" + + "\x12macsec_unsupported\x18\x97\x02 \x01(\bR\x11macsecUnsupported\x12:\n" + + "\x19gue_gre_decap_unsupported\x18\x98\x02 \x01(\bR\x16gueGreDecapUnsupported\x12R\n" + + "%mpls_label_classification_unsupported\x18\x99\x02 \x01(\bR\"mplsLabelClassificationUnsupported\x127\n" + + "\x17local_proxy_unsupported\x18\x9a\x02 \x01(\bR\x15localProxyUnsupported\x127\n" + + "\x17static_mpls_unsupported\x18\x9b\x02 \x01(\bR\x15staticMplsUnsupported\x12E\n" + + "\x1eqos_classification_unsupported\x18\x9c\x02 \x01(\bR\x1cqosClassificationUnsupported\x12C\n" + + "\x1dpolicy_forwarding_unsupported\x18\x9d\x02 \x01(\bR\x1bpolicyForwardingUnsupported\x12(\n" + + "\x0fcfm_unsupported\x18\x9e\x02 \x01(\bR\x0ecfmUnsupported\x127\n" + + "\x17label_range_unsupported\x18\x9f\x02 \x01(\bR\x15labelRangeUnsupported\x125\n" + + "\x16static_arp_unsupported\x18\xa0\x02 \x01(\bR\x14staticArpUnsupported\x12V\n" + + "'interface_policy_forwarding_unsupported\x18\xa1\x02 \x01(\bR$interfacePolicyForwardingUnsupported\x12?\n" + + "\x1duse_old_oc_path_static_lsp_nh\x18\xa2\x02 \x01(\bR\x17useOldOcPathStaticLspNh\x12>\n" + + "\x1bconfig_leaf_create_required\x18\xa3\x02 \x01(\bR\x18configLeafCreateRequired\x12:\n" + + "\x19skip_interface_name_check\x18\xa4\x02 \x01(\bR\x16skipInterfaceNameCheck\x12'\n" + + "\x0ffr_breakout_fix\x18\xa5\x02 \x01(\bR\rfrBreakoutFix\x12J\n" + + "!num_physical_channels_unsupported\x18\xa6\x02 \x01(\bR\x1enumPhysicalChannelsUnsupported\x12Q\n" + + "%unsupported_qos_output_service_policy\x18\xa7\x02 \x01(\bR!unsupportedQosOutputServicePolicy\x12V\n" + + "(interface_output_queue_non_standard_name\x18\xa8\x02 \x01(\bR#interfaceOutputQueueNonStandardName\x12Z\n" + + "*mpls_exp_ingress_classifier_oc_unsupported\x18\xa9\x02 \x01(\bR%mplsExpIngressClassifierOcUnsupported\x12I\n" + + "!default_no_igp_metric_propagation\x18\xaa\x02 \x01(\bR\x1ddefaultNoIgpMetricPropagation\x12S\n" + + "'skip_bgp_peer_group_send_community_type\x18\xab\x02 \x01(\bR!skipBgpPeerGroupSendCommunityType\x12J\n" + + "\"bgp_as_path_prepend_order_mismtach\x18\xac\x02 \x01(\bR\x1dbgpAsPathPrependOrderMismtach\x12g\n" + + "2explicit_swap_src_dst_mac_needed_for_loopback_mode\x18\xad\x02 \x01(\bR*explicitSwapSrcDstMacNeededForLoopbackMode\x127\n" + + "\x18link_local_instead_of_nh\x18\xae\x02 \x01(\bR\x14linkLocalInsteadOfNh\x12#\n" + + "\rlow_scale_aft\x18\xaf\x02 \x01(\bR\vlowScaleAft\x12S\n" + + "&missing_system_description_config_path\x18\xb0\x02 \x01(\bR\"missingSystemDescriptionConfigPath\x12C\n" + + "\x1enon_interval_fec_error_counter\x18\xb1\x02 \x01(\bR\x1anonIntervalFecErrorCounter\x12D\n" + + "\x1entp_source_address_unsupported\x18\xb2\x02 \x01(\bR\x1bntpSourceAddressUnsupported\x12C\n" + + "\x1estatic_mpls_lsp_oc_unsupported\x18\xb3\x02 \x01(\bR\x1astaticMplsLspOcUnsupported\x12H\n" + + " gre_decapsulation_oc_unsupported\x18\xb4\x02 \x01(\bR\x1dgreDecapsulationOcUnsupported\x12<\n" + + "\x1aisis_srgb_srlb_unsupported\x18\xb5\x02 \x01(\bR\x17isisSrgbSrlbUnsupported\x12X\n" + + ")isis_sr_prefix_segment_config_unsupported\x18\xb6\x02 \x01(\bR$isisSrPrefixSegmentConfigUnsupported\x12T\n" + + "'isis_sr_node_segment_config_unsupported\x18\xb7\x02 \x01(\bR\"isisSrNodeSegmentConfigUnsupported\x12]\n" + + ",policy_forwarding_to_next_hop_oc_unsupported\x18\xb8\x02 \x01(\bR&policyForwardingToNextHopOcUnsupported\x12E\n" + + "\x1fsflow_ingress_min_sampling_rate\x18\xb9\x02 \x01(\rR\x1bsflowIngressMinSamplingRate\x12:\n" + + "\x19qos_remark_oc_unsupported\x18\xba\x02 \x01(\bR\x16qosRemarkOcUnsupported\x12j\n" + + "2policy_forwarding_gre_encapsulation_oc_unsupported\x18\xbb\x02 \x01(\bR-policyForwardingGreEncapsulationOcUnsupported\x12M\n" + + "#policy_rule_counters_oc_unsupported\x18\xbc\x02 \x01(\bR\x1fpolicyRuleCountersOcUnsupported\x122\n" + + "\x15otn_to_eth_assignment\x18\xbd\x02 \x01(\bR\x12otnToEthAssignment\x12k\n" + + "3network_instance_import_export_policy_oc_unsuppored\x18\xbe\x02 \x01(\bR-networkInstanceImportExportPolicyOcUnsuppored\x12 \n" + + "\vskip_origin\x18\xbf\x02 \x01(\bR\n" + + "skipOrigin\x12:\n" + + "\x19predefined_max_ecmp_paths\x18\xc0\x02 \x01(\bR\x16predefinedMaxEcmpPaths\x12D\n" + + "\x1edecapsulate_gue_oc_unsupported\x18\xc1\x02 \x01(\bR\x1bdecapsulateGueOcUnsupported\x123\n" + + "\x15line_port_unsupported\x18\xc2\x02 \x01(\bR\x13linePortUnsupportedJ\x04\bT\x10UJ\x04\b\t\x10\n" + + "J\x04\b\x1c\x10\x1dJ\x04\b\x14\x10\x15J\x04\b&\x10'J\x04\b+\x10,J\x04\bZ\x10[J\x04\ba\x10bJ\x04\b7\x108J\x04\bY\x10ZJ\x04\b\x13\x10\x14J\x04\b$\x10%J\x04\b#\x10$J\x04\b(\x10)J\x04\bq\x10rJ\x06\b\x83\x01\x10\x84\x01J\x06\b\x8d\x01\x10\x8e\x01J\x06\b\xad\x01\x10\xae\x01J\x06\b\xea\x01\x10\xeb\x01J\x06\b\xfe\x01\x10\xff\x01J\x06\b\xe7\x01\x10\xe8\x01\x1a\xa0\x01\n" + + "\x12PlatformExceptions\x12A\n" + + "\bplatform\x18\x01 \x01(\v2%.openconfig.testing.Metadata.PlatformR\bplatform\x12G\n" + + "\n" + + "deviations\x18\x02 \x01(\v2'.openconfig.testing.Metadata.DeviationsR\n" + + "deviations\"\xab\x03\n" + + "\aTestbed\x12\x17\n" + + "\x13TESTBED_UNSPECIFIED\x10\x00\x12\x0f\n" + + "\vTESTBED_DUT\x10\x01\x12\x1a\n" + + "\x16TESTBED_DUT_DUT_4LINKS\x10\x02\x12\x1a\n" + + "\x16TESTBED_DUT_ATE_2LINKS\x10\x03\x12\x1a\n" + + "\x16TESTBED_DUT_ATE_4LINKS\x10\x04\x12\x1e\n" + + "\x1aTESTBED_DUT_ATE_9LINKS_LAG\x10\x05\x12\x1e\n" + + "\x1aTESTBED_DUT_DUT_ATE_2LINKS\x10\x06\x12\x1a\n" + + "\x16TESTBED_DUT_ATE_8LINKS\x10\a\x12\x15\n" + + "\x11TESTBED_DUT_400ZR\x10\b\x12\x1a\n" + + "\x16TESTBED_DUT_400ZR_PLUS\x10\t\x12!\n" + + "\x1dTESTBED_DUT_400ZR_100G_4LINKS\x10\n" + + "\x12!\n" + + "\x1dTESTBED_DUT_400FR_100G_4LINKS\x10\v\x12\x1a\n" + + "\x16TESTBED_DUT_ATE_5LINKS\x10\f\x12\x15\n" + + "\x11TESTBED_DUT_800ZR\x10\r\x12\x1a\n" + + "\x16TESTBED_DUT_800ZR_PLUS\x10\x0e\"m\n" + + "\x04Tags\x12\x14\n" + + "\x10TAGS_UNSPECIFIED\x10\x00\x12\x14\n" + + "\x10TAGS_AGGREGATION\x10\x01\x12\x18\n" + + "\x14TAGS_DATACENTER_EDGE\x10\x02\x12\r\n" + + "\tTAGS_EDGE\x10\x03\x12\x10\n" + + "\fTAGS_TRANSIT\x10\x04b\x06proto3" var ( file_metadata_proto_rawDescOnce sync.Once - file_metadata_proto_rawDescData = file_metadata_proto_rawDesc + file_metadata_proto_rawDescData []byte ) func file_metadata_proto_rawDescGZIP() []byte { file_metadata_proto_rawDescOnce.Do(func() { - file_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(file_metadata_proto_rawDescData) + file_metadata_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_metadata_proto_rawDesc), len(file_metadata_proto_rawDesc))) }) return file_metadata_proto_rawDescData } var file_metadata_proto_enumTypes = make([]protoimpl.EnumInfo, 2) var file_metadata_proto_msgTypes = make([]protoimpl.MessageInfo, 4) -var file_metadata_proto_goTypes = []interface{}{ +var file_metadata_proto_goTypes = []any{ (Metadata_Testbed)(0), // 0: openconfig.testing.Metadata.Testbed (Metadata_Tags)(0), // 1: openconfig.testing.Metadata.Tags (*Metadata)(nil), // 2: openconfig.testing.Metadata @@ -4059,61 +3649,11 @@ func file_metadata_proto_init() { if File_metadata_proto != nil { return } - if !protoimpl.UnsafeEnabled { - file_metadata_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_metadata_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata_Platform); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_metadata_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata_Deviations); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - file_metadata_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*Metadata_PlatformExceptions); i { - case 0: - return &v.state - case 1: - return &v.sizeCache - case 2: - return &v.unknownFields - default: - return nil - } - } - } type x struct{} out := protoimpl.TypeBuilder{ File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), - RawDescriptor: file_metadata_proto_rawDesc, + RawDescriptor: unsafe.Slice(unsafe.StringData(file_metadata_proto_rawDesc), len(file_metadata_proto_rawDesc)), NumEnums: 2, NumMessages: 4, NumExtensions: 0, @@ -4125,7 +3665,6 @@ func file_metadata_proto_init() { MessageInfos: file_metadata_proto_msgTypes, }.Build() File_metadata_proto = out.File - file_metadata_proto_rawDesc = nil file_metadata_proto_goTypes = nil file_metadata_proto_depIdxs = nil } diff --git a/testregistry.textproto b/testregistry.textproto index 3bd7011dfc8..e7260b79f5d 100644 --- a/testregistry.textproto +++ b/testregistry.textproto @@ -78,6 +78,30 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/acl/otg_tests/large_scale_acl/README.md" exec: " " } +test: { + id: "AFT-1.1" + description: "AFTs Base Link Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/afts/otg_tests/afts_base/README.md" + exec: "" +} +test: { + id: "AFT-2.1" + description: "AFTs Prefix Counters" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/afts/prefix_counters/otg_tests/afts_prefix_counters/README.md" + exec: "" +} +test: { + id: "AFT-3.1" + description: "AFTs Atomic Flag Check" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/afts/otg_tests/afts_atomic/README.md" + exec: "" +} +test: { + id: "AFT-5.1" + description: "AFTs DUT Reboot" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/afts/otg_tests/afts_reboot/README.md" + exec: "" +} test: { id: "attestz-1" description: "Validate attestz for initial install" @@ -253,6 +277,12 @@ test: { readme: "" exec: " " } +test: { + id: "DP-1.19" + description: "Egress traffic DSCP rewrite" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/egress_traffic_classification_and_rewrite_test/README.md" + exec: "" +} test: { id: "DP-1.2" description: "QoS policy feature config" @@ -262,7 +292,7 @@ test: { test: { id: "DP-1.3" description: "QoS ECN feature config" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ate_tests/qos_ecn_config_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/tests/qos_ecn_config_test/README.md" exec: " " } test: { @@ -295,11 +325,20 @@ test: { } test: { id: "DP-2.2" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/ingress_police_nhg/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ingress_policer/otg_tests/ingress_police_nhg/README.md" } test: { id: "DP-2.4" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/otg_tests/ingress_police_default/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ingress_policer/otg_tests/ingress_police_default/README.md" +} +test: { + id: "DP-2.5" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color/README.md" +} +test: { + id: "DP-2.6" + description: "Ingress policing with two rate, three color classifier" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/qos/ingress_policer/otg_tests/ingress_police_two_rate_three_color_with_classifier/README.md" } test: { id: "FP-1.1" @@ -324,6 +363,12 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/integrated_circuit/otg_tests/utilization/README.md" exec: " " } +test: { + id: "INT-1.1" + description: "Integrated Circuit Utilization and Thresholds" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/integrated_circuit/otg_tests/utilization_threshold/README.md" + exec: " " +} test: { id: "MGT-1" description: "Management HA test" @@ -336,6 +381,12 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/mpls/otg_tests/label_block/README.md" exec: " " } +test: { + id: "MPLS-1.2" + description: "MPLS Traffic Class Marking" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/mpls/otg_tests/mpls_tc/README.md" + exec: " " +} test: { id: "MTU-1.3" description: "Large IP Packet Transmission" @@ -436,12 +487,6 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/encapsulation/otg_tests/encap_gre_ipv4/README.md" exec: " " } -test: { - id: "PF-1.3" - description: "Policy-based Static GUE Encapsulation to IPv4 tunnel" - readme: " " - exec: " " -} test: { id: "PF-1.4" description: "Interface based GUE Decapsulation to IPv4 tunnel" @@ -468,13 +513,13 @@ test: { } test: { id: "PF-1.8" - description: "Ingress handling of the TTL, for all ingress packets decrement TTL , and for TTL=1 generate ICMP time exceeded message" - readme: " " + description: "Ingress handling of TTL" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/ttl/otg_tests/ingress/README.md" exec: " " }test: { id: "PF-1.9" - description: "Egress handling of the TTL, all egress packets should have their TTL decremented,egress packets with TTL=1 should generate ICMP time exceeded to source" - readme: " " + description: "Egress handling of TTL" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/ttl/otg_tests/egress/README.md" exec: " " } test: { @@ -554,6 +599,12 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/otg_tests/ipv4_gue_hashing_test/README.md" exec: " " } +test: { + id: "PF-1.23" + description: "EthoCWoMPLSoGRE IPV4 forwarding of IPV4/IPV6 payload" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/ethomplsogre/otg_tests/encap_decap_gre/README.md" + exec: " " +} test: { id: "PLT-1.1" description: "Interface breakout Test" @@ -600,7 +651,7 @@ test: { test: { id: "RT-1.8" description: "BGP Route Reflector test at scale" - readme: "" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/routereflector/otg_tests/route_reflector_scale/README.md" exec: " " } test: { @@ -612,7 +663,7 @@ test: { test: { id: "RT-1.10" description: "RT-1.10: BGP Keepalive and Holdtimer configuration" - readme: "" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/timers/otg_tests/bgp_keepalive_and_holdtimer_configuration_test/README.md" exec: " " } test: { @@ -708,7 +759,7 @@ test: { test: { id: "RT-1.26" description: "Basic static route support test" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/staticroute/static_route_support/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/staticroute/otg_tests/basic_static_route_support_test/README.md" exec: " " } test: { @@ -835,6 +886,24 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/bgp/multihop/README.md" exec: " " } +test: { + id: "RT-1.64" + description: "BGP Import/Export Policy Functional Test Case" + readme: "https://github.com/openconfig/feature/bgp/import-export-policy/otg_tests/README.md" + exec: " " +} +test: { + id: "RT-1.65" + description: "Static Route to Other Network-Instance Test Case" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/staticroute/otg_tests/other_network_instance/README.md" + exec: " " +} +test: { + id: "RT-1.66" + description: "IPv4 Static Route with IPv6 Next-Hop" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/staticroute/otg_tests/v4_static_route_with_v6_nh/README.md" + exec: " " +} test: { id: "RT-2.10" description: "IS-IS change LSP lifetime" @@ -872,14 +941,8 @@ test: { } test: { id: "RT-2.16" - description: "IS-IS Graceful Restart Helper" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/experimental/isis/otg_tests/graceful_restart_helper/README.md" - exec: " " -} -test: { - id: "RT-2.17" - description: "Graceful Restart Restarting" - readme: "https://github.com/openconfig/featureprofiles/feature/isis/otg_tests/graceful_restart_restarting/README.md" + description: "IS-IS Graceful Restart" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/graceful_restart/README.md" exec: " " } test: { @@ -908,7 +971,7 @@ test: { test: { id: "RT-2.9" description: "IS-IS metric style wide enabled" - readme: "" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/isis_metric_style_wide_enabled_test/README.md" exec: " " } test: { @@ -947,16 +1010,22 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/encapsulation/otg_tests/staticgueencap_and_bgp_path_selection/README.md" exec: " " } +test: { + id: "RT-3.53" + description: "Static route based GUE Encapsulation to IPv4 tunnel" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/static/otg_tests/next_hop_encap_udp/README.md" + exec: " " +} test: { id: "RT-4.10" description: "AFTs Route Summary" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/aft/aft_summary/otg_tests/route_summary_counters_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/afts/summary/otg_tests/route_summary_counters_test/README.md" exec: " " } test: { id: "RT-4.11" description: " Scale AFTs Route Summary" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/aft/aft_summary/otg_tests/scale_aft_summary/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/afts/summary/otg_tests/scale_aft_summary/README.md" exec: " " } @@ -1040,7 +1109,7 @@ test: { test: { id: "RT-6.1" description: "Core LLDP TLV Population" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/lldp/tests/core_lldp_tlv_population_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/lldp/otg_tests/core_lldp_tlv_population_test/README.md" exec: " " } test: { @@ -1140,6 +1209,11 @@ test: { description: "Static MAC address" readme: "" } +test: { + id: "RT-10.1" + description: "Default Route Generation based on 192.0.0.0/8 Presence" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/policy_forwarding/generate_route/otg_tests/generate_default_route/README.md" +} test: { id: "Replay-1.2" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/replay/tests/p4rt_replay/README.md" @@ -1150,6 +1224,11 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/sflow/otg_tests/sflow_base_test/README.md" exec: " " } +test: { + id: "Storage-1.1" + description: "Storage Error Counters" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/storage/README.md" +} test: { id: "System-1" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/tests/system_base_test/README.md" @@ -1409,7 +1488,7 @@ test: { test: { id: "TR-6.1" description: "system logging remote syslog" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/logging/remote_syslog/otg_tests/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/logging/otg_tests/remote_syslog/README.md" exec: " " } test: { @@ -1430,6 +1509,14 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_fec_uncorrectable_frames_test/README.md" exec: " " } +test: { + id: "TRANSCEIVER-101" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zr_800_platform_paths_test/README.md" +} +test: { + id: "TRANSCEIVER-102" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zr_800_terminal_device_paths_test/README.md" +} test: { id: "TRANSCEIVER-11" readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_logical_channels_test/README.md" @@ -1451,13 +1538,13 @@ test: { test: { id: "TRANSCEIVER-4" description: "400ZR Optics RX Input and TX Output Power streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_input_output_power_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/input_output_power/tests/zr_input_output_power_test/README.md" exec: " " } test: { id: "TRANSCEIVER-5" description: "400ZR channel frequency and output TX launch power setting" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_tunable_parameters_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tunable_parameters/tests/zr_tunable_parameters_test/README.md" exec: " " } test: { @@ -1469,7 +1556,7 @@ test: { test: { id: "TRANSCEIVER-8" description: "400ZR Optics module temperature streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/zr_temperature_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/temperature/tests/zr_temperature_test/README.md" exec: " " } test: { @@ -1580,6 +1667,12 @@ test: { readme: "" exec: " " } +test: { + id: "TUN-2.9" + description: "ECMP hashing based on outer and Inner header for MPLSoUDP packets" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/tunnel/otg_tests/mpls_over_udp_tunnel_hashing_test/README.md" + exec: " " +} test: { id: "TUN-2.10" description: "ECMP hashing based on outer and Inner header for GRE packets" @@ -1809,7 +1902,7 @@ test: { test: { id: "gNMI-1.4" description: "Telemetry: Inventory" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/ate_tests/telemetry_inventory_test/README.md" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/telemetry_inventory_test/README.md" exec: " " } test: { @@ -1818,6 +1911,12 @@ test: { readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gnmi/ate_tests/telemetry_port_speed_test/README.md" exec: " " } +test: { + id: "gNMI-1.6" + description: "gNMI server running in more than one network-instance" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/gnmi/tests/gnmi_ni_test/README.md" + exec: " " +} test: { id: "gNMI-1.8" description: "Configuration Metadata-only Retrieve and Replace" @@ -1967,14 +2066,110 @@ test: { exec: " " } test: { - id: "AFT-1.1" - description: "AFT Streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/aft/aft_base/otg_tests/aft_base/README.md" + id: "SYS-1.1" + description: "Test default COPP policy thresholds" + readme: "https://github.com/openconfig/featureprofiles/tree/main/feature/system/control_plane_traffic/otg_tests/default_copp_test/README.md" exec: " " } test: { - id: "AFT-2.1" - description: "AFT Streaming" - readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/aft/aft_base/otg_tests/aft_prefixcounters/README.md" + id: "URPF-1.1" + description: "uRPF validation from non-default network-instance" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/urpf/otg_tests/urpf_vrf_test/README.md" exec: " " } +test: { + id: "SYS-2.1" + description: "Ingress control-plane ACL." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/control_plane_traffic/otg_tests/ingress_acl/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/system/control_plane_traffic/otg_tests/ingress_acl/control_plane_traffic_ingress_acl_test.go" +} +test: { + id: "TRANSCEIVER-1 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Chromatic Dispersion(CD) telemetry values streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_cd_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_cd_test/zrp_cd_test.go" +} +test: { + id: "TRANSCEIVER-10 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Optics FEC(Forward Error Correction) Uncorrectable Frames Streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_fec_uncorrectable_frames_test/zrp_fec_uncorrectable_frames_test.go" +} +test: { + id: "TRANSCEIVER-3 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Optics firmware version streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_firmware_version_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_firmware_version_test/zrp_firmware_version_test.go" +} +test: { + id: "TRANSCEIVER-4 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS RX input and TX output power telemetry values streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_input_output_power_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_input_output_power_test/zrp_input_output_power_test.go" +} +test: { + id: "TRANSCEIVER-7 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Optics inventory info streaming" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_inventory_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_inventory_test/zrp_inventory_test.go" +} +test: { + id: "TRANSCEIVER-9 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS TX laser bias current telemetry values streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_laser_bias_current_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_laser_bias_current_test/zrp_laser_bias_current_test.go" +} +test: { + id: "TRANSCEIVER-11 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Optics logical channels provisioning and related telemetry." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_logical_channels_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_logical_channels_test/zrp_logical_channels_test.go" +} +test: { + id: "TRANSCEIVER-13 (400ZR_PLUS)" + description: "Configuration: 400ZR_PLUS Transceiver Low Power Mode Setting." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_low_power_mode_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_low_power_mode_test/zrp_low_power_mode_test.go" +} +test: { + id: "TRANSCEIVER-6 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Optics performance metrics (pm) streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_pm_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_pm_test/zrp_pm_test.go" +} +test: { + id: "TRANSCEIVER-12 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Transceiver Supply Voltage streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_supply_voltage_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_supply_voltage_test/zrp_supply_voltage_test.go" +} +test: { + id: "TRANSCEIVER-8 (400ZR_PLUS)" + description: "Telemetry: 400ZR_PLUS Optics module temperature streaming." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_temperature_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_temperature_test/zrp_temperature_test.go" +} +test: { + id: "TRANSCEIVER-5 (400ZR_PLUS)" + description: "Configuration: 400ZR_PLUS channel frequency, output TX launch power and operational mode setting." + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_tunable_parameters_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/platform/transceiver/tests/zrp_tunable_parameters_test/zrp_tunable_parameters_test.go" +} +test: { + id: "RT-14.2" + description: "GRIBI Route Test" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/gribi_route_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/gribi_route_test/gribi_route_test.go" +} +test: { + id: "RT-2.1" + description: "Base IS-IS Process and Adjacencies" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/base_adjacencies_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/isis/otg_tests/base_adjacencies_test/base_adjacencies_test.go" +} +test: { + id: "TE-3.31" + description: "Hierarchical weight resolution with PBF" + readme: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/README.md" + exec: "https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/hierarchical_weight_resolution_pbf_test/hierarchical_weight_resolution_pbf_test.go" +} diff --git a/tools/addrundata/suite.go b/tools/addrundata/suite.go index f40cd8ae301..ca24b67c9ce 100644 --- a/tools/addrundata/suite.go +++ b/tools/addrundata/suite.go @@ -1,13 +1,19 @@ package main import ( + "bufio" "fmt" "io/fs" "os" "path/filepath" + "regexp" "strings" ) +const featurePrefix = "feature/" + +var packageRegexp = regexp.MustCompile(`^package (\w+)`) + func errorf(format string, args ...any) { var buf strings.Builder fmt.Fprintf(&buf, format, args...) @@ -15,6 +21,43 @@ func errorf(format string, args ...any) { os.Stderr.WriteString(buf.String()) } +func getNonTestREADMEs(featureprofilesDir, nonTestREADMEsfilePath string) (map[string]bool, error) { + filePath := filepath.Join(featureprofilesDir, nonTestREADMEsfilePath) + file, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer file.Close() + + nonTestREADMEs := map[string]bool{} + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + if line != "" { + nonTestREADMEs[filepath.Join(featureprofilesDir, line)] = true + } + } + + if err := scanner.Err(); err != nil { + return nil, err + } + + return nonTestREADMEs, nil +} + +func validPath(testDir string) bool { + index := strings.Index(testDir, featurePrefix) + if index == -1 { + return false + } + testDir = testDir[index+len(featurePrefix):] + dirs := strings.Split(testDir, "/") + if len(dirs) < 3 || len(dirs) > 4 { + return false + } + return true +} + // testsuite maps from the test package directory to the various rundata extracted from // it. type testsuite map[string]*testcase @@ -24,15 +67,27 @@ type testsuite map[string]*testcase func (ts testsuite) read(featuredir string) (ok bool) { ok = true testdirs := map[string]bool{} + nonTestREADMEs, err := getNonTestREADMEs(filepath.Dir(featuredir), "tools/non_test_readmes.txt") + if err != nil { + return !ok + } - err := filepath.WalkDir(featuredir, func(path string, d fs.DirEntry, err error) error { + err = filepath.WalkDir(featuredir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } - if !strings.HasSuffix(path, "_test.go") { - return nil // Ignore anything that's not a test, including intermediate directories. + if !strings.HasSuffix(path, "README.md") { + return nil // Ignore anything that's not a README.md, including intermediate directories. + } + if nonTestREADMEs[path] { + return nil } testdir := filepath.Dir(path) + if !validPath(testdir) { + errorf("Test found in a bad path: %s", testdir) + ok = false + return nil + } if !isTestKind(testKind(testdir)) { relpath, err := filepath.Rel(filepath.Dir(featuredir), path) if err != nil { @@ -64,6 +119,9 @@ func (ts testsuite) read(featuredir string) (ok bool) { } errorf("Error reading testdir: %s: %v", reldir, err) ok = false + } else if err := checkGoTestFilePackageName(testdir); err != nil { + errorf("error checking test.go package name for dir: %s: %v", testdir, err) + ok = false } } @@ -271,3 +329,44 @@ func (ts testsuite) write(featuredir string) error { } return nil } + +// checkGoTestFilePackageName iterates through _test.go files in a directory. +func checkGoTestFilePackageName(testdir string) error { + files, err := os.ReadDir(testdir) + if err != nil { + return err + } + for _, file := range files { + if file.IsDir() || !strings.HasSuffix(file.Name(), "_test.go") { + continue + } + filePath := filepath.Join(testdir, file.Name()) + if err := checkPackageNameInFile(filePath); err != nil { + return fmt.Errorf("check failed for %s: %w", file.Name(), err) + } + } + return nil +} + +// checkPackageNameInFile handles a single file, allowing defer to work as expected. +func checkPackageNameInFile(filePath string) error { + f, err := os.Open(filePath) + if err != nil { + return err + } + defer f.Close() + + scanner := bufio.NewScanner(f) + packageName := "" + for scanner.Scan() { + match := packageRegexp.FindSubmatch(scanner.Bytes()) + if match != nil { + packageName = string(match[1]) + break + } + } + if !strings.HasSuffix(packageName, "_test") { + return fmt.Errorf("test file %s has package name %s, it should end with _test", filePath, packageName) + } + return nil +} diff --git a/tools/addrundata/suite_test.go b/tools/addrundata/suite_test.go index 1840a93dcff..e87b10dad24 100644 --- a/tools/addrundata/suite_test.go +++ b/tools/addrundata/suite_test.go @@ -17,6 +17,11 @@ import ( // ts.write(). func prepareSuite(featuredir string, ts testsuite) (testsuite, error) { newts := make(testsuite) + if err := os.MkdirAll(filepath.Join(filepath.Dir(featuredir), "tools"), 0700); err != nil { + return nil, err + } + nonTestReadmePath := filepath.Join(filepath.Dir(featuredir), "tools/non_test_readmes.txt") + os.Create(nonTestReadmePath) for reldir, tc := range ts { testdir := filepath.Join(featuredir, reldir) if err := os.MkdirAll(testdir, 0700); err != nil { @@ -34,6 +39,10 @@ func prepareSuite(featuredir string, ts testsuite) (testsuite, error) { if _, err := os.Create(testFilename); err != nil { return nil, fmt.Errorf("could not create %s: %w", testFilename, err) } + testPackage := "package foo_test" + if err := os.WriteFile(testFilename, []byte(testPackage), 0600); err != nil { + return nil, fmt.Errorf("could not write %s: %w", testFilename, err) + } newts[testdir] = &testcase{ markdown: &mpb.Metadata{ PlanId: tc.fixed.PlanId, @@ -52,14 +61,14 @@ func prepareSuite(featuredir string, ts testsuite) (testsuite, error) { func TestSuite_Read(t *testing.T) { featuredir := t.TempDir() want, err := prepareSuite(featuredir, testsuite{ - "foo/bar/ate_tests/qux_test": &testcase{ + "feature/foo/bar/ate_tests/qux_test": &testcase{ fixed: &mpb.Metadata{ Uuid: "c857db98-7b2c-433c-b9fb-4511b42edd78", PlanId: "XX-2.1", Description: "Qux Functional Test", }, }, - "foo/bar/otg_tests/qux_test": &testcase{ + "feature/foo/bar/otg_tests/qux_test": &testcase{ fixed: &mpb.Metadata{ Uuid: "c857db98-7b2c-433c-b9fb-4511b42edd78", PlanId: "XX-2.1", @@ -84,7 +93,7 @@ func TestSuite_Read(t *testing.T) { func TestSuite_Read_BadPath(t *testing.T) { featuredir := t.TempDir() _, err := prepareSuite(featuredir, testsuite{ - "foo/bar/qux_test": &testcase{ + "feature/foo/bar/qux_test": &testcase{ fixed: &mpb.Metadata{ Uuid: "c857db98-7b2c-433c-b9fb-4511b42edd78", PlanId: "XX-2.1", @@ -163,42 +172,42 @@ func TestSuite_Check(t *testing.T) { }{{ name: "NeedsUpdate", ts: testsuite{ - "foo/bar/tests/qux_test": quxMarkdownOnly, + "feature/foo/bar/tests/qux_test": quxMarkdownOnly, }, ok: false, }, { name: "Updated", ts: testsuite{ - "foo/bar/tests/qux_test": qux, - "foo/bar/tests/quuz_test": quuz, + "feature/foo/bar/tests/qux_test": qux, + "feature/foo/bar/tests/quuz_test": quuz, }, ok: true, }, { name: "DuplicateTestPlanID", ts: testsuite{ - "foo/bar/tests/qux_test": qux, - "foo/bar/tests/quuz_test": quuzDupPlanID, + "feature/foo/bar/tests/qux_test": qux, + "feature/foo/bar/tests/quuz_test": quuzDupPlanID, }, ok: false, }, { name: "DuplicateUUID", ts: testsuite{ - "foo/bar/tests/qux_test": qux, - "foo/bar/tests/quuz_test": quuzDupUUID, + "feature/foo/bar/tests/qux_test": qux, + "feature/foo/bar/tests/quuz_test": quuzDupUUID, }, ok: false, }, { name: "SameATEOTG", ts: testsuite{ - "foo/bar/ate_tests/qux_test": qux, - "foo/bar/otg_tests/qux_test": qux, + "feature/foo/bar/ate_tests/qux_test": qux, + "feature/foo/bar/otg_tests/qux_test": qux, }, ok: true, }, { name: "DifferentATEOTG", ts: testsuite{ - "foo/bar/ate_tests/qux_test": qux, - "foo/bar/otg_tests/qux_test": quuz, + "feature/foo/bar/ate_tests/qux_test": qux, + "feature/foo/bar/otg_tests/qux_test": quuz, }, ok: false, }} @@ -213,6 +222,61 @@ func TestSuite_Check(t *testing.T) { } } +func TestCheckGoTestFilePackageName(t *testing.T) { + tests := []struct { + desc string + filename string + content string + wantSuccess bool + }{ + { + desc: "valid package name", + filename: "foo_test.go", + content: "package foo_test", + wantSuccess: true, + }, + { + desc: "package name with extra characters", + filename: "foo_test.go", + content: "package foo_test_extra", + wantSuccess: false, + }, + { + desc: "package name with numbers", + filename: "foo123_test.go", + content: "package foo123_test", + wantSuccess: true, + }, + { + desc: "package name without _test suffix", + filename: "foo_test.go", + content: "package foo", + wantSuccess: false, + }, + } + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + tmpDir := t.TempDir() + testDir := filepath.Join(tmpDir, "/path/to/") + if err := os.MkdirAll(testDir, 0755); err != nil { + t.Fatalf("failed to create directory: %v", err) + } + fp := filepath.Join(testDir, tc.filename) + if _, err := os.Create(fp); err != nil { + t.Fatalf("failed to create file: %v", err) + } + if err := os.WriteFile(fp, []byte(tc.content), 0644); err != nil { + t.Fatalf("failed to write file: %v", err) + } + gotSuccess := checkGoTestFilePackageName(testDir) == nil + if gotSuccess != tc.wantSuccess { + t.Errorf("checkGoTestFilePackageName(%v) = %v, want: %v", testDir, gotSuccess, tc.wantSuccess) + } + }) + } +} + func TestSuite_Fix(t *testing.T) { quxMarkdownOnly := &testcase{ markdown: &mpb.Metadata{ @@ -227,16 +291,16 @@ func TestSuite_Fix(t *testing.T) { } ts := testsuite{ - "foo/bar/ate_tests/qux_test": copyCase(*quxMarkdownOnly), - "foo/bar/otg_tests/qux_test": copyCase(*quxMarkdownOnly), + "feature/foo/bar/ate_tests/qux_test": copyCase(*quxMarkdownOnly), + "feature/foo/bar/otg_tests/qux_test": copyCase(*quxMarkdownOnly), } if !ts.fix() { t.Error("testsuite.fix failed") } - ateFixed := ts["foo/bar/ate_tests/qux_test"].fixed - otgFixed := ts["foo/bar/otg_tests/qux_test"].fixed + ateFixed := ts["feature/foo/bar/ate_tests/qux_test"].fixed + otgFixed := ts["feature/foo/bar/otg_tests/qux_test"].fixed if diff := cmp.Diff(ateFixed, otgFixed, tcopts...); diff != "" { t.Errorf("After fix, ATE and OTG rundata differ (-ate,+otg):\n%s", diff) @@ -261,15 +325,15 @@ func checkMarkdowns(t testing.TB, featuredir string, ts testsuite, markdowns map func TestSuite_ReadFixWriteReadCheck(t *testing.T) { markdowns := map[string]*mpb.Metadata{ - "foo/bar/ate_tests/qux_test": { + "feature/foo/bar/ate_tests/qux_test": { PlanId: "XX-2.1", Description: "Qux Functional Test", }, - "foo/bar/otg_tests/qux_test": { + "feature/foo/bar/otg_tests/qux_test": { PlanId: "XX-2.1", Description: "Qux Functional Test", }, - "foo/bar/tests/quuz_test": { + "feature/foo/bar/tests/quuz_test": { PlanId: "XX-2.2", Description: "Quuz Functional Test", }, @@ -277,6 +341,11 @@ func TestSuite_ReadFixWriteReadCheck(t *testing.T) { // Populate the featuredir hierarchy with the README.md files and a dummy test file. featuredir := t.TempDir() + if err := os.MkdirAll(filepath.Join(filepath.Dir(featuredir), "tools"), 0700); err != nil { + t.Fatalf("Cannot create tools directory: %s", filepath.Join(filepath.Dir(featuredir), "tools")) + } + nonTestReadmePath := filepath.Join(filepath.Dir(featuredir), "tools/non_test_readmes.txt") + os.Create(nonTestReadmePath) for reldir, md := range markdowns { testdir := filepath.Join(featuredir, reldir) if err := os.MkdirAll(testdir, 0700); err != nil { diff --git a/tools/ci-trigger/cloudbuild.go b/tools/ci-trigger/cloudbuild.go index 01a19b457d9..56588c075f3 100644 --- a/tools/ci-trigger/cloudbuild.go +++ b/tools/ci-trigger/cloudbuild.go @@ -25,6 +25,7 @@ import ( "strings" "cloud.google.com/go/storage" + "github.com/golang/glog" "google.golang.org/api/cloudbuild/v1" "gopkg.in/yaml.v2" ) @@ -121,7 +122,33 @@ func createTGZArchive(f fs.FS) (*bytes.Buffer, error) { return err } - header, err := tar.FileInfoHeader(info, "") + // readLinkFS is the interface implemented by a file system that + // supports reading symbolic links. + // + // TODO(bstoll): readLinkFS is expected to be introduced in a future go + // release as fs.ReadLinkFS. Once fs.ReadLinkFS is available, we can + // remove this interface and add a test case to validate that the total + // number of files matches added to the archive matches the number + // intended to be there. For earlier versions of go we will ignore + // processing symlinks and log a warning. This interface allows for + // forward-compatibility with symlinks once it becomes available. + type readLinkFS interface { + ReadLink(name string) (string, error) + } + var link string + if info.Mode()&fs.ModeSymlink == fs.ModeSymlink { + if rfs, ok := f.(readLinkFS); ok { + link, err = rfs.ReadLink(path) + if err != nil { + return err + } + } else { + glog.Warningf("Skipping unsupported symlink %s in tar archive", path) + return nil + } + } + + header, err := tar.FileInfoHeader(info, link) if err != nil { return err } @@ -131,7 +158,7 @@ func createTGZArchive(f fs.FS) (*bytes.Buffer, error) { return err } - if d.IsDir() { + if d.IsDir() || link != "" { return nil } diff --git a/tools/ci-trigger/cloudbuild_test.go b/tools/ci-trigger/cloudbuild_test.go index a08c2506938..2a3fd32dc9b 100644 --- a/tools/ci-trigger/cloudbuild_test.go +++ b/tools/ci-trigger/cloudbuild_test.go @@ -29,6 +29,7 @@ func TestCreateTGZArchive(t *testing.T) { f := fstest.MapFS{ "file1.txt": {Data: []byte("Hello World"), Mode: 0644}, "file2.txt": {Mode: 0644}, + "symlink.txt": {Mode: fs.ModeSymlink, Data: []byte("file1.txt")}, "dir1": {Mode: fs.ModeDir | 0755}, "dir1/file3.txt": {Data: []byte("Inside dir1"), Mode: 0644}, "dir2": {Mode: fs.ModeDir | 0755}, diff --git a/tools/internal/canonicalocspec/canonicalocspec.go b/tools/internal/canonicalocspec/canonicalocspec.go new file mode 100644 index 00000000000..618ab989557 --- /dev/null +++ b/tools/internal/canonicalocspec/canonicalocspec.go @@ -0,0 +1,99 @@ +// Copyright 2025 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 +// +// https://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 canonicalocspec parses Canonical OCs from featureprofiles READMEs. +package canonicalocspec + +import ( + "bytes" + "fmt" + + "github.com/openconfig/featureprofiles/tools/internal/mdocspec" + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" + "github.com/openconfig/ygot/ytypes" + "github.com/yuin/goldmark" +) + +// ErrNotFound indicates the Canonical OC JSON block was not found or was invalid. +var ErrNotFound = fmt.Errorf("did not detect valid json block under a heading titled %q, please see https://github.com/openconfig/featureprofiles/blob/main/doc/test-requirements-template.md#canonical-oc for example", mdocspec.CanonicalOCHeading) + +// Parse extracts all the Canonical OCs from a featureprofiles README. +// If such a section is not found in the README, `ErrNotFound` will be +// returned. +// +// Expected markdown format: +// +// ## Canonical OC +// +// ```json +// { +// "interfaces": { +// "interface": [ +// { +// "config": { +// "description": "a description", +// "mtu": 1500, +// "name": "eth0", +// "type": "ethernetCsmacd" +// }, +// "hold-time": { +// "config": { +// "up": 42 +// } +// }, +// "name": "eth0" +// } +// ] +// }, +// "system": { +// "config": { +// "hostname": "a hostname" +// } +// } +// } +// ``` +// +// The first JSON code block after a heading line named exactly as +// "Canonical OC" will be parsed. Any other code blocks are +// ignored. +func Parse(source []byte) ([]ygot.GoStruct, error) { + var buf bytes.Buffer + md := goldmark.New( + goldmark.WithExtensions(mdocspec.MDJSONSpecs), + ) + if err := md.Convert(source, &buf); err != nil { + return nil, fmt.Errorf("MDJSONSpecs.Convert: %v", err) + } + if len(mdocspec.MDJSONSpecs.CanonicalOCs) == 0 { + return nil, ErrNotFound + } + var ocs []ygot.GoStruct + for _, oc := range mdocspec.MDJSONSpecs.CanonicalOCs { + ocStruct, err := getCanonicalOC([]byte(oc)) + if err != nil { + return nil, err + } + ocs = append(ocs, ocStruct) + } + return ocs, nil +} + +func getCanonicalOC(source []byte) (ygot.GoStruct, error) { + d := &oc.Root{} + if err := oc.Unmarshal(source, d, &ytypes.PreferShadowPath{}); err != nil { + return nil, err + } + return d, nil +} diff --git a/tools/internal/canonicalocspec/canonicalocspec_test.go b/tools/internal/canonicalocspec/canonicalocspec_test.go new file mode 100644 index 00000000000..4598464a93f --- /dev/null +++ b/tools/internal/canonicalocspec/canonicalocspec_test.go @@ -0,0 +1,255 @@ +// Copyright 2025 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 +// +// https://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 canonicalocspec + +import ( + "errors" + "testing" + + "github.com/openconfig/ondatra/gnmi/oc" + "github.com/openconfig/ygot/ygot" +) + +func TestParse(t *testing.T) { + wantOC := &oc.Root{} + intf := wantOC.GetOrCreateInterface("eth0") + intf.GetOrCreateHoldTime().SetUp(42) + intf.SetDescription("a description") + intf.SetType(oc.IETFInterfaces_InterfaceType_ethernetCsmacd) + intf.SetMtu(1500) + wantOC.GetOrCreateSystem().SetHostname("a hostname") + + tests := []struct { + desc string + readme string + wantErr bool + wantNotFoundErr bool + wantOCs []ygot.GoStruct + }{{ + desc: "valid-canonical-oc", + readme: `## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```", + wantErr: false, + wantNotFoundErr: false, + wantOCs: []ygot.GoStruct{wantOC}, + }, { + desc: "invalid-header", + readme: `## Invalid Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```", + wantErr: true, + wantNotFoundErr: true, + wantOCs: []ygot.GoStruct{}, + }, { + desc: "incomplete-canonical-oc", + readme: `## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } +}` + "\n```", + wantErr: true, + wantNotFoundErr: false, + wantOCs: []ygot.GoStruct{}, + }, { + desc: "incorrect-canonical-oc", + readme: `## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```", + wantErr: true, + wantNotFoundErr: false, + wantOCs: []ygot.GoStruct{}, + }, { + desc: "json-codeblock-missing", + readme: `## Canonical OC`, + wantErr: true, + wantNotFoundErr: true, + wantOCs: []ygot.GoStruct{}, + }, { + desc: "multiple-json-codeblocks", + readme: `## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + "\n```" + `json +{ + "openconfig-qos": { + "interfaces": [ + { + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "classifiers": [ + { + "classifier": "dest_A", + "config": { + "name": "dest_A", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb", + "new-leaf": "my_new_value" + } + } + }, + "interface": "PortChannel1.100" + } + ] + } +}` + "\n```", + wantErr: false, + wantNotFoundErr: false, + wantOCs: []ygot.GoStruct{wantOC}, + }} + + for _, tc := range tests { + t.Run(tc.desc, func(t *testing.T) { + ocs, err := Parse([]byte(tc.readme)) + if (err != nil) != tc.wantErr { + t.Fatalf("ParseCanonicalOC(%v) got error: %v, want error: %v", tc.readme, err, tc.wantErr) + } + if errors.Is(err, ErrNotFound) != tc.wantNotFoundErr { + t.Fatalf("ParseCanonicalOC(%v) got ErrNotFound: %v, want ErrNotFound: %v", tc.readme, err, tc.wantNotFoundErr) + } + if len(ocs) != len(tc.wantOCs) { + t.Fatalf("ParseCanonicalOC(%v) got %v, want %v", tc.readme, ocs, tc.wantOCs) + } + for idx, got := range ocs { + if diff, err := ygot.Diff(tc.wantOCs[idx], got); err != nil { + t.Errorf("ParseCanonicalOCs(%v) returned diff (-want +got):\n%s", tc.readme, diff) + } + } + }) + } +} diff --git a/tools/internal/mdocspec/md.go b/tools/internal/mdocspec/md.go index f1fc8601bae..c56db49b65d 100644 --- a/tools/internal/mdocspec/md.go +++ b/tools/internal/mdocspec/md.go @@ -27,6 +27,13 @@ const ( // OCSpecHeading is the MarkDown heading that MUST precede the yaml // section containing the OC path and RPC listing. OCSpecHeading = "OpenConfig Path and RPC Coverage" + // CanonicalOCHeading is the MarkDown heading that MUST precede the json + // section containing the OC path and RPC listing. + CanonicalOCHeading = "Canonical OC" + // yamlLang is the language identifier for yaml code blocks. + yamlLang = "yaml" + // jsonLang is the language identifier for json code blocks. + jsonLang = "json" ) type mdOCSpecs struct{} @@ -41,63 +48,119 @@ func (e *mdOCSpecs) Extend(m goldmark.Markdown) { m.SetRenderer(&yamlRenderer{}) } +type mdJSONSpecs struct { + CanonicalOCs []string +} + +// MDJSONSpecs is an extension that only renders the first json block from a +// functional test README that comes after the pre-established OC Spec heading +// `CanonicalOCHeading`. +var MDJSONSpecs = &mdJSONSpecs{} + +func (e *mdJSONSpecs) Extend(m goldmark.Markdown) { + // Clear the CanonicalOCs in the shared MDJSONSpecs when a new Markdown is created. + MDJSONSpecs.CanonicalOCs = MDJSONSpecs.CanonicalOCs[:0] + // NOMUTANTS -- This function call is required to extend the markdown and is actually tested. + extension.GFM.Extend(m) + m.SetRenderer(&jsonRenderer{}) +} + type yamlRenderer struct{} -func (r *yamlRenderer) Render(w io.Writer, source []byte, n ast.Node) error { - return ast.Walk(n, func(n ast.Node, entering bool) (ast.WalkStatus, error) { - return renderYAML(w, source, n, entering) +type jsonRenderer struct{} + +type codeBlockHandler func(c *ast.FencedCodeBlock, writer io.Writer, source []byte) (ast.WalkStatus, error) + +type renderCodeBlockArgs struct { + writer io.Writer + source []byte + node ast.Node + entering bool + specHeading string + lang string + processCodeBlock codeBlockHandler +} + +func processYAMLBlock(c *ast.FencedCodeBlock, writer io.Writer, source []byte) (ast.WalkStatus, error) { + l := c.Lines().Len() + for i := 0; i != l; i++ { + line := c.Lines().At(i) + if _, err := writer.Write(line.Value(source)); err != nil { + return ast.WalkStop, err + } + } + return ast.WalkStop, nil +} + +func (r *yamlRenderer) Render(writer io.Writer, source []byte, node ast.Node) error { + return ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + return renderCodeBlock(renderCodeBlockArgs{writer, source, node, entering, OCSpecHeading, yamlLang, processYAMLBlock}) + }) +} + +func processJSONBlock(c *ast.FencedCodeBlock, _ io.Writer, source []byte) (ast.WalkStatus, error) { + var curBytes []byte + l := c.Lines().Len() + for i := 0; i != l; i++ { + line := c.Lines().At(i) + curBytes = append(curBytes, line.Value(source)...) + } + if len(curBytes) != 0 { + MDJSONSpecs.CanonicalOCs = append(MDJSONSpecs.CanonicalOCs, string(curBytes)) + } + return ast.WalkContinue, nil +} + +func (r *jsonRenderer) Render(writer io.Writer, source []byte, node ast.Node) error { + return ast.Walk(node, func(node ast.Node, entering bool) (ast.WalkStatus, error) { + return renderCodeBlock(renderCodeBlockArgs{writer, source, node, entering, CanonicalOCHeading, jsonLang, processJSONBlock}) }) } func (r *yamlRenderer) AddOptions(...renderer.Option) {} -func ocSpecHeading(source []byte, n ast.Node) (heading *ast.Heading, ok bool) { - if h, ok := n.(*ast.Heading); ok { +func (r *jsonRenderer) AddOptions(...renderer.Option) {} + +func fetchHeading(source []byte, node ast.Node, specHeading string) (heading *ast.Heading, ok bool) { + if h, ok := node.(*ast.Heading); ok { if h.Lines().Len() == 0 { return nil, false } headingSegment := h.Lines().At(0) - if string(headingSegment.Value(source)) == OCSpecHeading { + if string(headingSegment.Value(source)) == specHeading { return h, true } } return nil, false } -func yamlCodeBlock(source []byte, n ast.Node) (block *ast.FencedCodeBlock, ok bool) { - if c, ok := n.(*ast.FencedCodeBlock); ok && c.Info != nil { - if lang := c.Info.Text(source); len(lang) > 0 && string(lang) == "yaml" { +func codeBlock(source []byte, node ast.Node, lang string) (block *ast.FencedCodeBlock, ok bool) { + if c, ok := node.(*ast.FencedCodeBlock); ok && c.Info != nil { + if l := c.Info.Text(source); len(l) > 0 && string(l) == lang { return c, true } } return nil, false } -func renderYAML(w io.Writer, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) { +func renderCodeBlock(args renderCodeBlockArgs) (ast.WalkStatus, error) { + entering := args.entering if !entering { return ast.WalkContinue, nil } - heading, ok := ocSpecHeading(source, n) + heading, ok := fetchHeading(args.source, args.node, args.specHeading) if !ok { return ast.WalkContinue, nil } // Check if prior to the next heading of the same level, - // a yaml code block can be found. + // a code block with the specified language can be found. for next := heading.NextSibling(); next != nil; next = next.NextSibling() { if h, ok := next.(*ast.Heading); ok && h.Level <= heading.Level { // End of heading reached. return ast.WalkContinue, nil } - if c, ok := yamlCodeBlock(source, next); ok { - l := c.Lines().Len() - for i := 0; i != l; i++ { - line := c.Lines().At(i) - if _, err := w.Write(line.Value(source)); err != nil { - return ast.WalkStop, err - } - } - // Stop after finding the first such YAML block. - return ast.WalkStop, nil + if c, ok := codeBlock(args.source, next, args.lang); ok { + return args.processCodeBlock(c, args.writer, args.source) } } return ast.WalkContinue, nil diff --git a/tools/internal/mdocspec/md_test.go b/tools/internal/mdocspec/md_test.go index 155846f2dc9..0517fcaf2a1 100644 --- a/tools/internal/mdocspec/md_test.go +++ b/tools/internal/mdocspec/md_test.go @@ -632,3 +632,1041 @@ This example yaml defines the OC paths intended to be covered by this test. OC }) } } + +func TestJSONRenderer(t *testing.T) { + tests := []struct { + desc string + inSource []byte + want []string + }{{ + desc: "valid-readme", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }, { + desc: "second-json-block-in-separate-heading", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## Second JSON Block +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 49 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }, { + desc: "two-json-blocks-same-heading", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 47 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + "\n```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 49 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 47 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }, { + desc: "json-block-after-next-heading-ignored", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC + +## Some other OC Heading +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{}, + }, { + desc: "no-json-blocks-last-heading", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC + +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{}, + }, { + desc: "empty-json-codeblock", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC +` + "```" + `json` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{}, + }, { + desc: "json-block-after-next-higher-heading-ignored", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC + +# Higher Heading +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{}, + }, { + desc: "json-block-after-next-lower-heading-accepted", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC + +### Lower Heading +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }, { + desc: "two-blocks-same-heading-first-language-not-specified-and-ignored", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC +` + "```" + + `{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + "\n```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 47 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 47 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }, { + desc: "valid-readme-with-two-canonical-ocs", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 48 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` + +## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 47 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 48 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +} +`, ` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 47 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }, { + desc: "valid-readme-with-todo", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{}, + }, { + desc: "readme-with-todo-and-valid-oc", + inSource: []byte(` +# RT-1.7: Local BGP Test + +## Summary + +The local\_bgp\_test brings up two OpenConfig controlled devices and tests that for an eBGP session + +* Established between them. +* Disconnected between them. +* Verify BGP neighbor parameters + +Enable an Accept-route all import-policy/export-policy for eBGP session under the BGP peer-group AFI/SAFI. + +This test is suitable for running in a KNE environment. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` + +## Canonical OC +` + "```" + `json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}` + "\n```" + ` +## OpenConfig Path and RPC Coverage +` + "```" + `yaml +paths: + ## Parameter Coverage + + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/afi-safis/afi-safi/apply-policy/config/export-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/import-policy: + /network-instances/network-instance/protocols/protocol/bgp/peer-groups/peer-group/apply-policy/config/export-policy: + +rpcs: + gnmi: + gNMI.Subscribe: + gNMI.Set: +` + "```"), + want: []string{` +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsmacd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +}`}, + }} + + for _, tt := range tests { + t.Run(tt.desc, func(t *testing.T) { + var buf strings.Builder + md := goldmark.New( + goldmark.WithExtensions(MDJSONSpecs), + ) + if err := md.Convert(tt.inSource, &buf); err != nil { + t.Fatalf("MDJSONSpecs.Convert(%v, &buf): %v", tt.inSource, err) + } + if len(tt.want) != len(MDJSONSpecs.CanonicalOCs) { + t.Fatalf("MDJSONSpecs.Convert(%s, &buf): got %v, want %v", string(tt.inSource), MDJSONSpecs.CanonicalOCs, tt.want) + } + for idx, got := range MDJSONSpecs.CanonicalOCs { + if diff := cmp.Diff(strings.TrimSpace(tt.want[idx]), strings.TrimSpace(got)); diff != "" { + t.Errorf("MDJSONSpecs.Convert(%s, &buf): at idx: %d, (-want, +got):\n%s", string(tt.inSource), idx, diff) + } + } + }) + } +} diff --git a/tools/non_test_readmes.txt b/tools/non_test_readmes.txt new file mode 100644 index 00000000000..46d72aa8976 --- /dev/null +++ b/tools/non_test_readmes.txt @@ -0,0 +1,3 @@ +feature/gnsi/certz/test_data/README.md +feature/p4rt/README.md +feature/gnsi/acctz/README.md diff --git a/tools/validate_paths.go b/tools/validate_paths.go deleted file mode 100644 index 84ab210886c..00000000000 --- a/tools/validate_paths.go +++ /dev/null @@ -1,422 +0,0 @@ -// Copyright 2022 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 - -// https://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. - -// validate_paths inspects paths in the Feature Profiles and fails if any are not standard -// OpenConfig paths. -package main - -import ( - "errors" - "fmt" - "io/fs" - "os" - "path" - "path/filepath" - "reflect" - "sort" - "strings" - - "flag" - - log "github.com/golang/glog" - fppb "github.com/openconfig/featureprofiles/proto/feature_go_proto" - "github.com/openconfig/goyang/pkg/yang" - "github.com/openconfig/ygot/util" - "github.com/protocolbuffers/txtpbfmt/parser" - "google.golang.org/protobuf/encoding/prototext" -) - -var ( - featuresRootFlag = flag.String("feature_root", "", "root directory of the feature profiles") - yangRootsFlag = flag.String( - "yang_roots", "", "comma separated list of directories containing .yang files.", - ) - yangSkipsFlag = flag.String( - "yang_skip_roots", "", "sub-directories of the .yang roots which should be ignored.", - ) - featureFilesFlag = flag.String("feature_files", "", "optional file containing list of feature.textprotos to validate instead of checking all files. If not specified, then all files will be checked. Note that all files will still be checked and annotated, but only these files will cause failure.") -) - -var ( - featuresRoot string - yangPaths []string - skipYANGDirs = map[string]bool{} -) - -type pathType int - -const ( - unset pathType = iota - configuration - telemetry -) - -// addKnownPaths records information about all paths in and under a `yang.Entity`. -func addKnownPaths(ps map[string]pathType, e *yang.Entry) { - if e.IsLeaf() || e.IsLeafList() { - pt := unset - switch util.IsConfig(e) { - case true: - pt = configuration - case false: - pt = telemetry - } - ps[fmt.Sprintf("%q", util.SchemaTreePathNoModule(e))] = pt - return - } - for _, ce := range util.Children(e) { - addKnownPaths(ps, ce) - } -} - -func yangFiles(root string) ([]string, error) { - ps := map[string]bool{} - err := filepath.WalkDir(root, func(p string, info fs.DirEntry, err error) error { - if err != nil { - return err - } - if info == nil { - return nil - } - if info.IsDir() { - if skipYANGDirs[p] { - fmt.Println("Skipping definitions in", p) - return filepath.SkipDir - } - return nil - } - if strings.HasSuffix(p, ".yang") { - ps[p] = true - } - return nil - }) - if err != nil { - return nil, err - } - res := make([]string, 0, len(ps)) - for p := range ps { - res = append(res, p) - } - return res, nil -} - -func modules() (map[string]*yang.Module, error) { - var files, dirs []string - for _, p := range yangPaths { - ds, err := yang.PathsWithModules(p) - if err != nil { - return nil, err - } - dirs = append(dirs, ds...) - - fs, err := yangFiles(p) - if err != nil { - return nil, err - } - files = append(files, fs...) - } - - ms := yang.NewModules() - - ms.AddPath(dirs...) - - for _, p := range files { - p = path.Base(p) - if err := ms.Read(p); err != nil { - return nil, fmt.Errorf("ms.Read(%s): %v", p, err) - } - } - - if errs := ms.Process(); len(errs) != 0 { - log.Error("ms.Process errors:") - for _, e := range errs { - log.Error(" ", e) - } - return nil, errors.New("yang module process error") - } - return ms.Modules, nil -} - -type line struct { - line int32 - column int32 - oc string - detail string -} - -type file struct { - name string - lines []line - // Errors which are not correlated with a line. - errors []string -} - -func (f file) githubAnnotations() string { - var b strings.Builder - for _, errLine := range f.errors { - b.WriteString(fmt.Sprintf("::%s file=%s::%s\n", "error", f.name, errLine)) - } - - for _, line := range f.lines { - // https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message - b.WriteString(fmt.Sprintf("::%s file=%s,line=%d,col=%d::%s %s\n", "error", f.name, line.line, line.column, line.detail, line.oc)) - } - - return b.String() -} - -func constructValidProfiles(files []string) (map[string]bool, map[string]*file) { - tmp := fppb.FeatureProfile{} - validProfiles := make(map[string]bool) - reports := make(map[string]*file) - - for _, f := range files { - report := reports[f] - if report == nil { - report = &file{name: f} - reports[f] = report - } - bs, err := os.ReadFile(f) - if err != nil { - // Just accumulate the file error since we can't do anything else. - report.errors = append(report.errors, err.Error()) - continue - } - - // Unmarshal will report syntax errors (although generally without line numbers). - if err := prototext.Unmarshal(bs, &tmp); err != nil { - report.errors = append(report.errors, err.Error()) - } - - // Validate feature profile ID name by checking path. - targetFeatureProfileName := getFeatureProfileNameFromPath(f) - featureProfileIDName := tmp.GetId().GetName() - if targetFeatureProfileName != featureProfileIDName { - report.errors = append(report.errors, featureProfileIDName+" is inconsistent with path, want "+targetFeatureProfileName) - } else { - validProfiles[featureProfileIDName] = true - } - } - return validProfiles, reports -} - -// checkFiles parses all `path:` lines in the input `files`, reporting any syntax errors and paths -// which are not in the `knownOC` set. -func checkFiles(knownOC map[string]pathType, files []string, validProfiles map[string]bool, reports map[string]*file) error { - tmp := fppb.FeatureProfile{} - - log.Infof("%d files to validate", len(files)) - - for _, f := range files { - log.Infof("Validating file: %v", f) - report := reports[f] - if report == nil { - report = &file{name: f} - reports[f] = report - } - - bs, err := os.ReadFile(f) - if err != nil { - // Just accumulate the file error since we can't do anything else. - report.errors = append(report.errors, err.Error()) - continue - } - - // Unmarshal will report syntax errors (although generally without line numbers). - if err := prototext.Unmarshal(bs, &tmp); err != nil { - report.errors = append(report.errors, err.Error()) - } - - // Use parser.Parse so I can get line numbers for OC paths we don't recognize. - ast, err := parser.Parse(bs) - if err != nil { - return err - } - for _, a := range ast { - switch a.Name { - case "config_path", "telemetry_path": - for _, c := range a.Children { - if c.Name == "path" { - for _, v := range c.Values { - var detail string - switch knownOC[v.Value] { - case configuration: - if a.Name != "config_path" { - detail = fmt.Sprintf("erroneously labeled %s", a.Name) - } - case telemetry: - if a.Name != "telemetry_path" { - detail = fmt.Sprintf("erroneously labeled %s", a.Name) - } - case unset: - detail = "missing from YANG" - } - if detail != "" { - report.lines = append(report.lines, line{ - line: c.Start.Line, - column: c.Start.Column, - oc: v.Value, - detail: detail, - }) - } - } - } - } - case "feature_profile_dependency": - for _, c := range a.Children { - if c.Name == "name" { - for _, v := range c.Values { - profileName := v.Value[1 : len(v.Value)-1] // Trim quotes - if !validProfiles[profileName] { - report.lines = append(report.lines, line{ - line: c.Start.Line, - column: c.Start.Column, - detail: "cannot find feature profile dependency " + profileName, - }) - } - } - } - } - } - } - } - return nil -} - -// cleanReports removes any empty reports. -func cleanReports(reports map[string]*file) { - for key, report := range reports { - if reflect.DeepEqual(report, &file{name: report.name}) { - delete(reports, key) - } - } -} - -// getFeatureProfileNameFromPath gets feature profile id.name from path. -func getFeatureProfileNameFromPath(file string) string { - featureProfileFilePath := strings.ReplaceAll(strings.TrimPrefix(file, featuresRoot), "/", " ") - featureProfileFilePathArray := strings.Fields(featureProfileFilePath) - featureProfileFilePathArray = featureProfileFilePathArray[0 : len(featureProfileFilePathArray)-1] - return strings.Join(featureProfileFilePathArray, "_") -} - -// featureFiles returns the feature files to check and all existing feature files. -func featureFiles() (map[string]struct{}, []string, error) { - var allFiles []string - filesToCheck := map[string]struct{}{} - err := filepath.WalkDir(featuresRoot, - func(path string, e fs.DirEntry, err error) error { - if err != nil { - return err - } - if e.IsDir() { - return nil - } - if e.Name() == "feature.textproto" { - allFiles = append(allFiles, path) - filesToCheck[path] = struct{}{} - } - return nil - }) - if err != nil { - return nil, nil, err - } - sort.Strings(allFiles) - - if *featureFilesFlag != "" { - filesToCheck = map[string]struct{}{} - readBytes, err := os.ReadFile(*featureFilesFlag) - if err != nil { - log.Fatalf("cannot read feature_files flag: %v", err) - } - for _, line := range strings.Split(string(readBytes), "\n") { - line = strings.TrimSpace(line) - if line == "" { - continue - } - path, err := filepath.Abs(line) - if err != nil { - return nil, nil, err - } - filesToCheck[path] = struct{}{} - } - } - - return filesToCheck, allFiles, nil -} - -// Check that every OC path used in the Feature Profiles is defined in the public OpenConfig yang. -func main() { - flag.Parse() - if *featuresRootFlag == "" { - log.Fatal("feature_root must be set.") - } - if *yangRootsFlag == "" { - log.Fatal("yang_roots must be set.") - } - featuresRoot = *featuresRootFlag - yangPaths = strings.Split(*yangRootsFlag, ",") - for _, s := range strings.Split(*yangSkipsFlag, ",") { - skipYANGDirs[s] = true - } - - ms, err := modules() - if err != nil { - log.Fatal(err) - } - knownPaths := map[string]pathType{} - for _, m := range ms { - addKnownPaths(knownPaths, yang.ToEntry(m)) - } - - filesToCheck, allFiles, err := featureFiles() - if err != nil { - log.Fatal(err) - } - - validProfiles, reports := constructValidProfiles(allFiles) - if err := checkFiles(knownPaths, allFiles, validProfiles, reports); err != nil { - log.Fatal(err) - } - - cleanReports(reports) - - log.Infof("%d files must pass: %v", len(filesToCheck), filesToCheck) - if len(reports) == 0 { - return - } - - msg := []string{"Feature paths inconsistent with YANG schema:"} - failed := false - for _, f := range reports { - fmt.Print(f.githubAnnotations()) - if _, ok := filesToCheck[f.name]; ok { - failed = true - } - msg = append(msg, " file: "+f.name) - if len(f.errors) != 0 { - msg = append(msg, " toplevel errors:") - for _, e := range f.errors { - msg = append(msg, " "+e) - } - } - for _, l := range f.lines { - msg = append(msg, fmt.Sprintf(" line %d: %s %s", l.line, l.detail, l.oc)) - } - } - log.Info(strings.Join(msg, "\n")) - if failed { - os.Exit(1) - } -} diff --git a/tools/validate_readme_canonicalocspec/testdata/invalid_incorrect_canonical_oc.md b/tools/validate_readme_canonicalocspec/testdata/invalid_incorrect_canonical_oc.md new file mode 100644 index 00000000000..84f4c8d68cc --- /dev/null +++ b/tools/validate_readme_canonicalocspec/testdata/invalid_incorrect_canonical_oc.md @@ -0,0 +1,161 @@ +--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +# Instructions for this template + +Below is the required template for writing test requirements. Good examples of +test requirements include: + +* [TE-18.1 gRIBI MPLS in UDP Encapsulation and + Decapsulation](https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_in_udp/README.md) +* [TE-3.7: Base Hierarchical NHG + Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md) + * [PF-1.14 - MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload](https://github.com/openconfig/featureprofiles/blob/61d11a47d37fac58b311f57e8c2b619dc7fb264b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md) +* [gNMI-1.13: Telemetry: Optics Power and Bias + Current](https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/optics_power_and_bias_current_test/README.md) + +# TestID-x.y: Short name of test here + +## Summary + +Write a few sentences or paragraphs describing the purpose and scope of the +test. + +## Testbed type + +* Specify the .testbed topology file from the + [topologies](https://github.com/openconfig/featureprofiles/tree/main/topologies) + folder to be used with this test + +## Procedure + +### Test environment setup + +* Description of procedure to configure ATE and DUT with pre-requisites making + it possible to cover the intended paths and RPCs. + +### TestID-x.y.1 - Name of subtest 1 + +The following steps are typically present in each subtest. + +* Step 1 - Generate DUT configuration + +Replace this JSON formatted content with the "Canonical OC" that is expected to +be generated by the subtest. This configuration should be in JSON format. + +NOTE: If the test requires configuration or state which are not defined in the +[public OC data models]([url](https://github.com/openconfig/public)), then new OC +paths must be proposed in this README. A "TODO" comment must be provided linking +to a pull request to add these paths to the public OC models repository. The +README may be approved and merged before the TODO item(s) are resolved. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy + +```json +{ + "openconfig-qos": { + "interfaces": [ + { + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "classifiers": [ + { + "classifier": "dest_A", + "config": { + "name": "dest_A", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb", + "new-leaf": "my_new_value" + } + } + }, + "interface": "PortChannel1.100" + } + ] + } +} +``` + +* Step 2 - Push configuration to DUT using gnmi.Set with REPLACE option +* Step 3 - Send Traffic +* Step 4 - Validation with pass/fail criteria + +### TestID-x.y.2 - Name of subtest 2 + +Repeat the format of the first subtest for each additional subtest defined. + +#### Canonical OC +```json +{ + "interfaces": { + "interface": [ + { + "config": { + "description": "a description", + "mtu": 1500, + "name": "eth0", + "type": "ethernetCsd" + }, + "hold-time": { + "config": { + "up": 42 + } + }, + "name": "eth0" + } + ] + }, + "system": { + "config": { + "hostname": "a hostname" + } + } +} +``` + +## OpenConfig Path and RPC Coverage + +This yaml stanza defines the OC paths intended to be covered by this test. OC +paths used for test environment setup are not required to be listed here. This +content is parsed by automation to derive the test coverage. If any new OC +paths are required, they should also be included here as a TODO comment. + +```yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis and linecard components + /components/component/state/name: + platform_type: ["CHASSIS", "LINECARD"] + # TODO: Add next-hop-group paths, see https://github.com/openconfig/public/pull/1234 + # /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name + # /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * MFF - A modular form factor device containing LINECARDs, FABRIC and + redundant CONTROLLER_CARD components + * FFF - fixed form factor + * vRX - virtual router device diff --git a/tools/validate_readme_canonicalocspec/testdata/invalid_incorrect_json_format.md b/tools/validate_readme_canonicalocspec/testdata/invalid_incorrect_json_format.md new file mode 100644 index 00000000000..211e90ba099 --- /dev/null +++ b/tools/validate_readme_canonicalocspec/testdata/invalid_incorrect_json_format.md @@ -0,0 +1,196 @@ +--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +# Instructions for this template + +Below is the required template for writing test requirements. Good examples of +test requirements include: + +* [TE-18.1 gRIBI MPLS in UDP Encapsulation and + Decapsulation](https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_in_udp/README.md) +* [TE-3.7: Base Hierarchical NHG + Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md) + * [PF-1.14 - MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload](https://github.com/openconfig/featureprofiles/blob/61d11a47d37fac58b311f57e8c2b619dc7fb264b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md) +* [gNMI-1.13: Telemetry: Optics Power and Bias + Current](https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/optics_power_and_bias_current_test/README.md) + +# TestID-x.y: Short name of test here + +## Summary + +Write a few sentences or paragraphs describing the purpose and scope of the +test. + +## Testbed type + +* Specify the .testbed topology file from the + [topologies](https://github.com/openconfig/featureprofiles/tree/main/topologies) + folder to be used with this test + +## Procedure + +### Test environment setup + +* Description of procedure to configure ATE and DUT with pre-requisites making + it possible to cover the intended paths and RPCs. + +### TestID-x.y.1 - Name of subtest 1 + +The following steps are typically present in each subtest. + +* Step 1 - Generate DUT configuration + +Replace this JSON formatted content with the "Canonical OC" that is expected to +be generated by the subtest. This configuration should be in JSON format. + +NOTE: If the test requires configuration or state which are not defined in the +[public OC data models]([url](https://github.com/openconfig/public)), then new OC +paths must be proposed in this README. A "TODO" comment must be provided linking +to a pull request to add these paths to the public OC models repository. The +README may be approved and merged before the TODO item(s) are resolved. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy + +#### Canonical OC + +```json +{ + "openconfig-qos": { + "interfaces": [ + { + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "classifiers": [ + { + "classifier": "dest_A", + "config": { + "name": "dest_A", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb", + "new-leaf": "my_new_value" + } + } + }, + "interface": "PortChannel1.100", + } + ] + } +} +``` + +* Step 2 - Push configuration to DUT using gnmi.Set with REPLACE option +* Step 3 - Send Traffic +* Step 4 - Validation with pass/fail criteria + +### TestID-x.y.2 - Name of subtest 2 + +Repeat the format of the first subtest for each additional subtest defined. + +#### Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "config": { + "neighbor-address": "192.0.2.6" + }, + "neighbor-address": "192.0.2.6", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "config": { + "peer-group-name": "peer_group" + }, + "peer-group-name": "peer_group", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +This yaml stanza defines the OC paths intended to be covered by this test. OC +paths used for test environment setup are not required to be listed here. This +content is parsed by automation to derive the test coverage. If any new OC +paths are required, they should also be included here as a TODO comment. + +```yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis and linecard components + /components/component/state/name: + platform_type: ["CHASSIS", "LINECARD"] + # TODO: Add next-hop-group paths, see https://github.com/openconfig/public/pull/1234 + # /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name + # /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * MFF - A modular form factor device containing LINECARDs, FABRIC and + redundant CONTROLLER_CARD components + * FFF - fixed form factor + * vRX - virtual router device diff --git a/tools/validate_readme_canonicalocspec/testdata/invalid_no_canonical_oc.md b/tools/validate_readme_canonicalocspec/testdata/invalid_no_canonical_oc.md new file mode 100644 index 00000000000..34859436fce --- /dev/null +++ b/tools/validate_readme_canonicalocspec/testdata/invalid_no_canonical_oc.md @@ -0,0 +1,193 @@ +--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +# Instructions for this template + +Below is the required template for writing test requirements. Good examples of +test requirements include: + +* [TE-18.1 gRIBI MPLS in UDP Encapsulation and + Decapsulation](https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_in_udp/README.md) +* [TE-3.7: Base Hierarchical NHG + Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md) + * [PF-1.14 - MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload](https://github.com/openconfig/featureprofiles/blob/61d11a47d37fac58b311f57e8c2b619dc7fb264b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md) +* [gNMI-1.13: Telemetry: Optics Power and Bias + Current](https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/optics_power_and_bias_current_test/README.md) + +# TestID-x.y: Short name of test here + +## Summary + +Write a few sentences or paragraphs describing the purpose and scope of the +test. + +## Testbed type + +* Specify the .testbed topology file from the + [topologies](https://github.com/openconfig/featureprofiles/tree/main/topologies) + folder to be used with this test + +## Procedure + +### Test environment setup + +* Description of procedure to configure ATE and DUT with pre-requisites making + it possible to cover the intended paths and RPCs. + +### TestID-x.y.1 - Name of subtest 1 + +The following steps are typically present in each subtest. + +* Step 1 - Generate DUT configuration + +Replace this JSON formatted content with the "Canonical OC" that is expected to +be generated by the subtest. This configuration should be in JSON format. + +NOTE: If the test requires configuration or state which are not defined in the +[public OC data models]([url](https://github.com/openconfig/public)), then new OC +paths must be proposed in this README. A "TODO" comment must be provided linking +to a pull request to add these paths to the public OC models repository. The +README may be approved and merged before the TODO item(s) are resolved. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy + +```json +{ + "openconfig-qos": { + "interfaces": [ + { + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "classifiers": [ + { + "classifier": "dest_A", + "config": { + "name": "dest_A", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb", + "new-leaf": "my_new_value" + } + } + }, + "interface": "PortChannel1.100" + } + ] + } +} +``` + +* Step 2 - Push configuration to DUT using gnmi.Set with REPLACE option +* Step 3 - Send Traffic +* Step 4 - Validation with pass/fail criteria + +### TestID-x.y.2 - Name of subtest 2 + +Repeat the format of the first subtest for each additional subtest defined. + +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "config": { + "neighbor-address": "192.0.2.6" + }, + "neighbor-address": "192.0.2.6", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "config": { + "peer-group-name": "peer_group" + }, + "peer-group-name": "peer_group", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +This yaml stanza defines the OC paths intended to be covered by this test. OC +paths used for test environment setup are not required to be listed here. This +content is parsed by automation to derive the test coverage. If any new OC +paths are required, they should also be included here as a TODO comment. + +```yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis and linecard components + /components/component/state/name: + platform_type: ["CHASSIS", "LINECARD"] + # TODO: Add next-hop-group paths, see https://github.com/openconfig/public/pull/1234 + # /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name + # /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * MFF - A modular form factor device containing LINECARDs, FABRIC and + redundant CONTROLLER_CARD components + * FFF - fixed form factor + * vRX - virtual router device diff --git a/tools/validate_readme_canonicalocspec/testdata/valid_readme.md b/tools/validate_readme_canonicalocspec/testdata/valid_readme.md new file mode 100644 index 00000000000..e97db6725f5 --- /dev/null +++ b/tools/validate_readme_canonicalocspec/testdata/valid_readme.md @@ -0,0 +1,194 @@ +--- +name: New featureprofiles test requirement +about: Use this template to document the requirements for a new test to be implemented. +title: '' +labels: enhancement +assignees: '' +--- + +# Instructions for this template + +Below is the required template for writing test requirements. Good examples of +test requirements include: + +* [TE-18.1 gRIBI MPLS in UDP Encapsulation and + Decapsulation](https://github.com/openconfig/featureprofiles/blob/main/feature/gribi/otg_tests/mpls_in_udp/README.md) +* [TE-3.7: Base Hierarchical NHG + Update](/feature/gribi/otg_tests/base_hierarchical_nhg_update/README.md) + * [PF-1.14 - MPLSoGRE IPV4 encapsulation of IPV4/IPV6 payload](https://github.com/openconfig/featureprofiles/blob/61d11a47d37fac58b311f57e8c2b619dc7fb264b/feature/policy_forwarding/otg_tests/mpls_gre_ipv4_encap_test/README.md) +* [gNMI-1.13: Telemetry: Optics Power and Bias + Current](https://github.com/openconfig/featureprofiles/blob/main/feature/platform/tests/optics_power_and_bias_current_test/README.md) + +# TestID-x.y: Short name of test here + +## Summary + +Write a few sentences or paragraphs describing the purpose and scope of the +test. + +## Testbed type + +* Specify the .testbed topology file from the + [topologies](https://github.com/openconfig/featureprofiles/tree/main/topologies) + folder to be used with this test + +## Procedure + +### Test environment setup + +* Description of procedure to configure ATE and DUT with pre-requisites making + it possible to cover the intended paths and RPCs. + +### TestID-x.y.1 - Name of subtest 1 + +The following steps are typically present in each subtest. + +* Step 1 - Generate DUT configuration + +Replace this JSON formatted content with the "Canonical OC" that is expected to +be generated by the subtest. This configuration should be in JSON format. + +NOTE: If the test requires configuration or state which are not defined in the +[public OC data models]([url](https://github.com/openconfig/public)), then new OC +paths must be proposed in this README. A "TODO" comment must be provided linking +to a pull request to add these paths to the public OC models repository. The +README may be approved and merged before the TODO item(s) are resolved. + +#### TODO: https://github.com/openconfig/public/pull/1234 - Add new leaf to scheduler-policy + +```json +{ + "openconfig-qos": { + "interfaces": [ + { + "config": { + "interface-id": "PortChannel1.100" + }, + "input": { + "classifiers": [ + { + "classifier": "dest_A", + "config": { + "name": "dest_A", + "type": "IPV4" + } + } + ], + "scheduler-policy": { + "config": { + "name": "limit_group_A_1Gb", + "new-leaf": "my_new_value" + } + } + }, + "interface": "PortChannel1.100" + } + ] + } +} +``` + +* Step 2 - Push configuration to DUT using gnmi.Set with REPLACE option +* Step 3 - Send Traffic +* Step 4 - Validation with pass/fail criteria + +### TestID-x.y.2 - Name of subtest 2 + +Repeat the format of the first subtest for each additional subtest defined. + +#### Canonical OC +```json +{ + "network-instances": { + "network-instance": [ + { + "config": { + "name": "DEFAULT" + }, + "name": "DEFAULT", + "protocols": { + "protocol": [ + { + "bgp": { + "neighbors": { + "neighbor": [ + { + "config": { + "neighbor-address": "192.0.2.6" + }, + "neighbor-address": "192.0.2.6", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + }, + "peer-groups": { + "peer-group": [ + { + "config": { + "peer-group-name": "peer_group" + }, + "peer-group-name": "peer_group", + "timers": { + "config": { + "hold-time": 30, + "keepalive-interval": 10 + } + } + } + ] + } + }, + "config": { + "identifier": "BGP", + "name": "BGP" + }, + "identifier": "BGP", + "name": "BGP" + } + ] + } + } + ] + } +} +``` + +## OpenConfig Path and RPC Coverage + +This yaml stanza defines the OC paths intended to be covered by this test. OC +paths used for test environment setup are not required to be listed here. This +content is parsed by automation to derive the test coverage. If any new OC +paths are required, they should also be included here as a TODO comment. + +```yaml +paths: + # interface configuration + /interfaces/interface/config/description: + /interfaces/interface/config/enabled: + # name of chassis and linecard components + /components/component/state/name: + platform_type: ["CHASSIS", "LINECARD"] + # TODO: Add next-hop-group paths, see https://github.com/openconfig/public/pull/1234 + # /network-instances/network-instance/static/next-hop-groups/next-hop-group/config/name + # /network-instances/network-instance/protocols/protocol/static-routes/static/next-hops/next-hop/config/next-hop + +rpcs: + gnmi: + gNMI.Set: + union_replace: true + gNMI.Subscribe: + on_change: true +``` + +## Required DUT platform + +* Specify the minimum DUT-type: + * MFF - A modular form factor device containing LINECARDs, FABRIC and + redundant CONTROLLER_CARD components + * FFF - fixed form factor + * vRX - virtual router device diff --git a/tools/validate_readme_canonicalocspec/validate_readme_canonicalocspec.go b/tools/validate_readme_canonicalocspec/validate_readme_canonicalocspec.go new file mode 100644 index 00000000000..c2750cf23bb --- /dev/null +++ b/tools/validate_readme_canonicalocspec/validate_readme_canonicalocspec.go @@ -0,0 +1,166 @@ +// 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 +// +// https://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. + +// Command validate_readme_canonicalocspec validates Canonical OCs listed by MarkDown +// (READMEs) against the most recent repository states in +// github.com/openconfig/featureprofiles. +package main + +import ( + goflag "flag" + "io/fs" + "os" + "path/filepath" + "strings" + + log "github.com/golang/glog" + "github.com/openconfig/featureprofiles/tools/internal/canonicalocspec" + "github.com/openconfig/featureprofiles/tools/internal/fpciutil" + "github.com/openconfig/ygot/ygot" + flag "github.com/spf13/pflag" + "golang.org/x/exp/maps" +) + +// Config is the set of flags for this binary. +type Config struct { + FeatureDir string + NonTestREADMEs stringMap +} + +func newConfig() *Config { + return &Config{ + NonTestREADMEs: map[string]struct{}{}, + } +} + +type stringMap map[string]struct{} + +func (m stringMap) String() string { + return strings.Join(maps.Keys(m), ",") +} + +func (m stringMap) Type() string { + return "stringMap" +} + +func (m stringMap) Set(readmePath string) error { + m[readmePath] = struct{}{} + return nil +} + +// New registers a flagset with the configuration needed by this binary. +func New(fs *flag.FlagSet) *Config { + c := newConfig() + + if fs == nil { + fs = flag.CommandLine + } + fs.StringVar(&c.FeatureDir, "feature-dir", "", "path to the feature directory of featureprofiles, for which all README.md files are validated for their coverage spec") + fs.Var(&c.NonTestREADMEs, "non-test-readme", "README that's exempt from coverage spec validation (can be specified multiple times)") + + return c +} + +var ( + config *Config +) + +func init() { + config = New(nil) +} + +func readmeFiles(featureDir string) ([]string, error) { + var files []string + err := filepath.WalkDir(featureDir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if d.Name() != fpciutil.READMEname { + return nil + } + + files = append(files, path) + return nil + }) + return files, err +} + +func isValidOC(canonicalOC ygot.GoStruct) error { + return ygot.ValidateGoStruct(canonicalOC) +} + +func main() { + flag.CommandLine.AddGoFlagSet(goflag.CommandLine) // for compatibility with glog + flag.Parse() + + fileCount := flag.NArg() + var files []string + switch { + case fileCount != 0 && config.FeatureDir != "": + log.Exit("If -feature-dir flag is specified, README files must not be specified as positional arguments.") + case fileCount != 0: + files = flag.Args() + case config.FeatureDir == "": + var err error + config.FeatureDir, err = fpciutil.FeatureDir() + if err != nil { + log.Exitf("Unable to locate feature root: %v", err) + } + fallthrough + case config.FeatureDir != "": + var err error + files, err = readmeFiles(config.FeatureDir) + if err != nil { + log.Exitf("Error gathering README.md files for validation: %v", err) + } + default: + log.Exit("Program internal error: input not handled.") + } + + erredFiles := map[string]struct{}{} + for _, file := range files { + if _, ok := config.NonTestREADMEs[file]; ok { + // Allowlist + continue + } + + log.Infof("Validating %q", file) + b, err := os.ReadFile(file) + if err != nil { + log.Exitf("Error reading file: %q", file) + } + ocStructs, err := canonicalocspec.Parse(b) + if err != nil { + log.Errorf("file %v: %v", file, err) + erredFiles[file] = struct{}{} + continue + } + + errored := false + for _, ocStruct := range ocStructs { + if err := isValidOC(ocStruct); err != nil { + log.Errorf("%q contains invalid Canonical OCs: %v", file, err) + errored = true + } + } + if errored { + erredFiles[file] = struct{}{} + } else { + log.Infof("%q contains %d valid Canonical OCs\n", file, len(ocStructs)) + } + } + if len(erredFiles) > 0 { + log.Exitf("The following files have errors:\n%v", strings.Join(maps.Keys(erredFiles), "\n")) + } +} diff --git a/tools/validate_readme_canonicalocspec/validate_readme_canonicalocspec_test.sh b/tools/validate_readme_canonicalocspec/validate_readme_canonicalocspec_test.sh new file mode 100755 index 00000000000..ab5f732ca12 --- /dev/null +++ b/tools/validate_readme_canonicalocspec/validate_readme_canonicalocspec_test.sh @@ -0,0 +1,39 @@ +# Copyright 2025 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 +# +# https://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. + +#!/bin/bash + +go install ./ + +filename=invalid_incorrect_canonical_oc.md +if validate_readme_canonicalocspec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=invalid_no_canonical_oc.md +if validate_readme_canonicalocspec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=invalid_incorrect_json_format.md +if validate_readme_canonicalocspec --alsologtostderr testdata/"${filename}"; then + echo "Validation passed, but failure expected" + exit 1 +fi +filename=valid_readme.md +if ! validate_readme_canonicalocspec --alsologtostderr testdata/"${filename}"; then + echo "Validation failed, but pass expected" + exit 1 +fi +echo "PASS" \ No newline at end of file diff --git a/tools/validate_readme_spec/validate_readme_spec_test.sh b/tools/validate_readme_spec/validate_readme_spec_test.sh index c13ac7bd1e6..6f6a4f45ca8 100755 --- a/tools/validate_readme_spec/validate_readme_spec_test.sh +++ b/tools/validate_readme_spec/validate_readme_spec_test.sh @@ -41,4 +41,4 @@ if ! validate_readme_spec --alsologtostderr testdata/"${filename}"; then echo "Validation failed, but pass expected" exit 1 fi -echo "PASS" +echo "PASS" \ No newline at end of file diff --git a/topologies/binding/binding.go b/topologies/binding/binding.go index d47f7ce18eb..4c556163874 100644 --- a/topologies/binding/binding.go +++ b/topologies/binding/binding.go @@ -42,6 +42,7 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/grpc/metadata" ) var ( @@ -197,27 +198,82 @@ func (d *staticDUT) DialP4RT(ctx context.Context, opts ...grpc.DialOption) (p4pb func (d *staticDUT) DialCLI(context.Context) (binding.CLIClient, error) { sshOpts := d.r.ssh(d.dev) - c := &ssh.ClientConfig{ + config := &ssh.ClientConfig{ User: sshOpts.Username, Auth: []ssh.AuthMethod{ ssh.Password(sshOpts.Password), ssh.KeyboardInteractive(sshInteractive(sshOpts.Password)), }, } + sc, err := createSSHClient(config, sshOpts) + if err != nil { + return nil, err + } + return newCLI(sc) +} + +func (d *staticDUT) DialSSH(_ context.Context, sshAuth binding.SSHAuth) (binding.SSHClient, error) { + var config *ssh.ClientConfig + switch auth := sshAuth.(type) { + case binding.PasswordAuth: + config = &ssh.ClientConfig{ + User: auth.User, + Auth: []ssh.AuthMethod{ + ssh.Password(auth.Password), + ssh.KeyboardInteractive(sshInteractive(auth.Password)), + }, + } + case binding.KeyAuth: + signer, err := ssh.ParsePrivateKey(auth.Key) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %w", err) + } + config = &ssh.ClientConfig{ + User: auth.User, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + } + case binding.CertificateAuth: + signer, err := ssh.ParsePrivateKey(auth.PrivateKey) + if err != nil { + return nil, fmt.Errorf("failed to parse private key: %w", err) + } + cert, _, _, _, err := ssh.ParseAuthorizedKey(auth.Certificate) + if err != nil { + return nil, fmt.Errorf("failed to parse certificate: %w", err) + } + signer, err = ssh.NewCertSigner(cert.(*ssh.Certificate), signer) + if err != nil { + return nil, fmt.Errorf("failed to create certificate signer: %w", err) + } + config = &ssh.ClientConfig{ + User: auth.User, + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(signer), + }, + } + default: + return nil, fmt.Errorf("ssh auth type %T not supported yet", auth) + } + sc, err := createSSHClient(config, d.r.ssh(d.dev)) + if err != nil { + return nil, err + } + return newSSH(sc) +} + +func createSSHClient(config *ssh.ClientConfig, sshOpts *bindpb.Options) (*ssh.Client, error) { if sshOpts.SkipVerify { - c.HostKeyCallback = ssh.InsecureIgnoreHostKey() + config.HostKeyCallback = ssh.InsecureIgnoreHostKey() } else { cb, err := knownHostsCallback() if err != nil { return nil, err } - c.HostKeyCallback = cb - } - sc, err := ssh.Dial("tcp", sshOpts.Target, c) - if err != nil { - return nil, err + config.HostKeyCallback = cb } - return newCLI(sc) + return ssh.Dial("tcp", sshOpts.Target, config) } // For every question asked in an interactive login ssh session, set the answer to user password. @@ -560,10 +616,26 @@ type creds struct { secure bool } -func (c *creds) GetRequestMetadata(_ context.Context, _ ...string) (map[string]string, error) { +func (c *creds) GetRequestMetadata(ctx context.Context, _ ...string) (map[string]string, error) { + md, ok := metadata.FromOutgoingContext(ctx) + var username, password string + if ok { + if len(md.Get("username")) > 0 && md.Get("username")[0] != "" { + username = md.Get("username")[0] + } + if len(md.Get("password")) > 0 && md.Get("password")[0] != "" { + password = md.Get("password")[0] + } + } + if username == "" { + username = c.username + } + if password == "" { + password = c.password + } return map[string]string{ - "username": c.username, - "password": c.password, + "username": username, + "password": password, }, nil } diff --git a/topologies/binding/ssh.go b/topologies/binding/ssh.go new file mode 100644 index 00000000000..c90395aaa78 --- /dev/null +++ b/topologies/binding/ssh.go @@ -0,0 +1,68 @@ +// Copyright 2025 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 binding + +import ( + "context" + "fmt" + + "github.com/openconfig/ondatra/binding" + "golang.org/x/crypto/ssh" +) + +// sshClient implements the binding.SSHClient interface using an SSH client. +type sshClient struct { + *binding.AbstractSSHClient + ssh *ssh.Client +} + +func newSSH(sc *ssh.Client) (*sshClient, error) { + return &sshClient{ssh: sc}, nil +} + +func (c *sshClient) RunCommand(_ context.Context, cmd string) (binding.CommandResult, error) { + session, err := c.ssh.NewSession() + if err != nil { + return nil, fmt.Errorf("could not create session: %w", err) + } + defer session.Close() + + out, err := session.CombinedOutput(cmd) + switch err.(type) { + case nil: + return &commandResult{output: string(out)}, nil + case *ssh.ExitError, *ssh.ExitMissingError: + return &commandResult{output: string(out), error: err.Error()}, nil + default: + return nil, err + } +} + +func (c *sshClient) Close() error { + return c.ssh.Close() +} + +type commandResult struct { + *binding.AbstractCommandResult + output, error string +} + +func (r *commandResult) Output() string { + return r.output +} + +func (r *commandResult) Error() string { + return r.error +} diff --git a/topologies/binding/ssh_test.go b/topologies/binding/ssh_test.go new file mode 100644 index 00000000000..bb61b799e1d --- /dev/null +++ b/topologies/binding/ssh_test.go @@ -0,0 +1,249 @@ +// Copyright 2025 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 binding + +import ( + "bufio" + "context" + "encoding/binary" + "errors" + "fmt" + "log" + "net" + "os" + "strings" + "syscall" + "testing" + + "github.com/google/go-cmp/cmp" + "golang.org/x/crypto/ssh" +) + +// sshClientFixture configures a local SSH server and client connected over +// a socket pair. The socketpair, server transport and client +// transport will implicitly close when the handshake fails, or if the +// connection is closed. +type sshClientFixture struct { + sshClient *sshClient +} + +var sshServerSigner ssh.Signer + +func init() { + var err error + sshServerSigner, err = ssh.ParsePrivateKey(sshServerKeyPEM()) + if err != nil { + panic(err) + } +} + +func (f *sshClientFixture) start(t testing.TB) error { + fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM, 0) + if err != nil { + return err + } + + serverFile := os.NewFile(uintptr(fds[0]), "socketpair[0]") + defer serverFile.Close() // Does not affect serverConn. + serverConn, err := net.FileConn(serverFile) + if err != nil { + return err + } + + clientFile := os.NewFile(uintptr(fds[1]), "socketpair[1]") + defer clientFile.Close() // Does not affect clientConn. + clientConn, err := net.FileConn(clientFile) + if err != nil { + return err + } + + serverConfig := &ssh.ServerConfig{ + PasswordCallback: func(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) { + if conn.User() == "alice" && string(password) == "bob" { + return nil, nil + } + return nil, errors.New("login error") + }, + } + serverConfig.AddHostKey(sshServerSigner) + + clientConfig := &ssh.ClientConfig{ + User: "alice", + Auth: []ssh.AuthMethod{ssh.Password("bob")}, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + // Server and client handshakes must be done simultaneously. + // Conveniently, once we pass the net.Conn to ssh.NewServerConn and + // ssh.NewClientConn, they hand the net.Conn to a mux which will + // close the socket when handshake fails or when ssh.Client closes, + // so we do not need to close the server or client transport. + + // Obtain the handshake status from the server and the client. + errch := make(chan error) + defer close(errch) + + go func() { + t.Log("Server begins handshake.") + _, serverChans, serverReq, err := ssh.NewServerConn(serverConn, serverConfig) + if err != nil { + errch <- fmt.Errorf("server error: %w", err) + return + } + go f.handleServerNewChannel(serverChans) + go ssh.DiscardRequests(serverReq) + + t.Log("Server is ready.") + errch <- nil + }() + + var client *ssh.Client + + go func() { + t.Log("Client begins handshake.") + clientTransport, clientChans, clientReq, err := ssh.NewClientConn(clientConn, "socketpair", clientConfig) + if err != nil { + errch <- fmt.Errorf("client error: %w", err) + return + } + + t.Log("Client is ready.") + client = ssh.NewClient(clientTransport, clientChans, clientReq) + errch <- nil + }() + + if err1, err2 := <-errch, <-errch; err1 != nil || err2 != nil { + return fmt.Errorf("handshake errors: %v; and %v", err1, err2) + } + + sshClient, err := newSSH(client) + if err != nil { + return err + } + f.sshClient = sshClient + return nil +} + +func (f *sshClientFixture) handleServerNewChannel(ch <-chan ssh.NewChannel) { + for newChannel := range ch { + if newChannel.ChannelType() != "session" { + newChannel.Reject(ssh.UnknownChannelType, "unknown channel type") + continue + } + channel, requests, err := newChannel.Accept() + if err != nil { + log.Printf("Could not accept channel: %v", err) + continue + } + go f.handleServerChannel(channel, requests) + } +} + +// handleServerChannel provides a basic session for testing that +// echoes stdin lines to either stdout (default) or stderr (if stdin +// line begins with "stderr"), alone with whether shell or pty has +// been requested. +// +// Example interaction: +// +// - stdin: hello +// - stdout: nopty noshell hello +// - pty-req +// - stdin: hello again +// - stdout: pty noshell hello again +// - shell +// - stdin: stderr hello +// - stderr: pty shell stderr hello +func (f *sshClientFixture) handleServerChannel(c ssh.Channel, reqs <-chan *ssh.Request) { + var shell, pty bool + r := bufio.NewReader(c) + + go func() { + for req := range reqs { + switch req.Type { + case "shell": + shell = true + req.Reply(true, nil) + case "pty-req": + pty = true + req.Reply(true, nil) + case "exec": + f.handleExec(c, req) + default: + req.Reply(false, nil) + } + } + }() + + for { + line, err := r.ReadString('\n') + if err != nil { + return // io.EOF or otherwise. + } + + output := fmt.Sprintf("%s %s %s", + which(pty, "pty", "nopty"), + which(shell, "shell", "noshell"), + line) + if strings.HasPrefix(line, "stderr") { + c.Stderr().Write([]byte(output)) + } else { + c.Write([]byte(output)) + } + } +} + +// handleExec provides a command that prints the executed command to +// stdout, a message to stderr, and exit status 0. The output may be +// out of order. +// +// - stdout: exec command: +// - stderr: exec stderr +func (f *sshClientFixture) handleExec(c ssh.Channel, req *ssh.Request) { + var execMsg struct{ Command string } + if err := ssh.Unmarshal(req.Payload, &execMsg); err != nil { + req.Reply(false, nil) + return + } + req.Reply(true, nil) + output := fmt.Sprintf("exec command: %s\n", execMsg.Command) + c.Write([]byte(output)) + c.Stderr().Write([]byte("exec stderr\n")) + c.CloseWrite() + + statusMsg := make([]byte, 4) + binary.BigEndian.PutUint32(statusMsg, 0) + c.SendRequest("exit-status", false, statusMsg) + c.Close() +} + +func TestSSH(t *testing.T) { + f := &sshClientFixture{} + if err := f.start(t); err != nil { + t.Fatalf("Could not start sshClientFixture: %v", err) + } + t.Log("Test is ready.") + defer f.sshClient.Close() + + res, err := f.sshClient.RunCommand(context.Background(), "xyzzy") + if err != nil { + t.Fatalf("Could not execute command: %v", err) + } + got := strings.Split(res.Output(), "\n") + want := []string{"exec command: xyzzy", "exec stderr", ""} + if diff := cmp.Diff(want, got, cmpSortStrings); diff != "" { + t.Errorf("Command output -want, +got:\n%s", diff) + } +} diff --git a/topologies/dut_800zr.testbed b/topologies/dut_800zr.testbed new file mode 100644 index 00000000000..73a9360a95d --- /dev/null +++ b/topologies/dut_800zr.testbed @@ -0,0 +1,23 @@ +# proto-file: github.com/openconfig/ondatra/blob/main/proto/testbed.proto +# proto-message: ondatra.Testbed + +# 1 DUT, 2 port, 800ZR optics + +duts { + id: "dut" + ports { + id: "port1" + speed: S_800GB + pmd: PMD_800GBASE_ZR + } + ports { + id: "port2" + speed: S_800GB + pmd: PMD_800GBASE_ZR + } +} + +links { + a: "dut:port1" + b: "dut:port2" +} diff --git a/topologies/dut_800zr_plus.testbed b/topologies/dut_800zr_plus.testbed new file mode 100644 index 00000000000..7f2b4e79862 --- /dev/null +++ b/topologies/dut_800zr_plus.testbed @@ -0,0 +1,23 @@ +# proto-file: github.com/openconfig/ondatra/blob/main/proto/testbed.proto +# proto-message: ondatra.Testbed + +# 1 DUT, 2 port, 800ZR_PLUS optics + +duts { + id: "dut" + ports { + id: "port1" + speed: S_800GB + pmd: PMD_800GBASE_ZR_PLUS + } + ports { + id: "port2" + speed: S_800GB + pmd: PMD_800GBASE_ZR_PLUS + } +} + +links { + a: "dut:port1" + b: "dut:port2" +} diff --git a/topologies/kne/cisco/8000e/config.cfg b/topologies/kne/cisco/8000e/config.cfg index 4b7d6814533..ac7ae401d0d 100644 --- a/topologies/kne/cisco/8000e/config.cfg +++ b/topologies/kne/cisco/8000e/config.cfg @@ -4,7 +4,7 @@ username cisco group root-lr group cisco-support - password 7 01100F175804575D72 + secret 10 $6$nd8Pp1emKxgJDp1.$PeQSLpqjl3esYN6QmpmWPFuS.34wiD7Fb7cxVvx8sQXpvvPwhXVUx5pFm4vRxxrV.qK7uhFKCzdhyDDgXXirE. ! interface Loopback0 ipv4 address 44.44.44.44 255.255.255.255 diff --git a/topologies/kne/cisco/xrd/config.cfg b/topologies/kne/cisco/xrd/config.cfg index 4b7d6814533..ac7ae401d0d 100644 --- a/topologies/kne/cisco/xrd/config.cfg +++ b/topologies/kne/cisco/xrd/config.cfg @@ -4,7 +4,7 @@ username cisco group root-lr group cisco-support - password 7 01100F175804575D72 + secret 10 $6$nd8Pp1emKxgJDp1.$PeQSLpqjl3esYN6QmpmWPFuS.34wiD7Fb7cxVvx8sQXpvvPwhXVUx5pFm4vRxxrV.qK7uhFKCzdhyDDgXXirE. ! interface Loopback0 ipv4 address 44.44.44.44 255.255.255.255 diff --git a/topologies/kne/nokia/srlinux/topology.textproto b/topologies/kne/nokia/srlinux/topology.textproto index b3d7fed2c37..8f730a01ed3 100644 --- a/topologies/kne/nokia/srlinux/topology.textproto +++ b/topologies/kne/nokia/srlinux/topology.textproto @@ -98,6 +98,30 @@ nodes: { name: "ethernet-1/9" } } + interfaces: { + key: "e1-10" + value: { + name: "ethernet-1/10" + } + } + interfaces: { + key: "e1-11" + value: { + name: "ethernet-1/11" + } + } + interfaces: { + key: "e1-12" + value: { + name: "ethernet-1/12" + } + } + interfaces: { + key: "e1-13" + value: { + name: "ethernet-1/13" + } + } } nodes: { name: "dut2" @@ -196,6 +220,30 @@ nodes: { name: "ethernet-1/9" } } + interfaces: { + key: "e1-10" + value: { + name: "ethernet-1/10" + } + } + interfaces: { + key: "e1-11" + value: { + name: "ethernet-1/11" + } + } + interfaces: { + key: "e1-12" + value: { + name: "ethernet-1/12" + } + } + interfaces: { + key: "e1-13" + value: { + name: "ethernet-1/13" + } + } } nodes: { name: "otg" @@ -203,6 +251,9 @@ nodes: { version: "0.0.1-9999" # Please update this with the local version from ixiatg-configmap.yaml interfaces: { key: "eth1" + value: { + group: "lag" + } } interfaces: { key: "eth2" @@ -252,6 +303,60 @@ nodes: { group: "lag" } } + interfaces: { + key: "eth10" + value: { + group: "lag" + } + } + interfaces: { + key: "eth11" + value: { + group: "lag" + } + } + interfaces: { + key: "eth12" + value: { + group: "lag" + } + } + interfaces: { + key: "eth13" + value: { + group: "lag" + } + } + interfaces: { + key: "eth14" + value: { + group: "lag" + } + } + interfaces: { + key: "eth15" + value: { + group: "lag" + } + } + interfaces: { + key: "eth16" + value: { + group: "lag" + } + } + interfaces: { + key: "eth17" + value: { + group: "lag" + } + } + interfaces: { + key: "eth18" + value: { + group: "lag" + } + } } links: { a_node: "otg"